From 2e0d42def55f53ef712e4552d762efe5b1cfd553 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 9 Jul 2019 10:07:14 -0300 Subject: [PATCH 1/6] Compiler: check abstract method return type --- spec/compiler/semantic/abstract_def_spec.cr | 56 +++++++++++++++++++ .../crystal/semantic/abstract_def_checker.cr | 24 +++++++- 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/spec/compiler/semantic/abstract_def_spec.cr b/spec/compiler/semantic/abstract_def_spec.cr index c1b6c1e72009..583b889897e3 100644 --- a/spec/compiler/semantic/abstract_def_spec.cr +++ b/spec/compiler/semantic/abstract_def_spec.cr @@ -394,4 +394,60 @@ describe "Semantic: abstract def" do end ) end + + it "errors if missing return type" do + assert_error %( + abstract class Foo + abstract def foo : Int32 + end + + class Bar < Foo + def foo + 1 + end + end + ), + "this method overrides Foo#foo() which has an explicit return type of Int32: please add an explicit return type to this method as well" + end + + it "errors if different return type" do + assert_error %( + abstract class Foo + abstract def foo : Int32 + end + + class Bar < Foo + struct Int32 + end + + def foo : Int32 + 1 + end + end + ), + "this method must return Int32, which is the return type of the overwritten method Foo#foo(), not Bar::Int32" + end + + it "can return a more specific type" do + assert_type(%( + class Parent + end + + class Child < Parent + end + + + abstract class Foo + abstract def foo : Parent + end + + class Bar < Foo + def foo : Child + Child.new + end + end + + Bar.new.foo + )) { types["Child"] } + end end diff --git a/src/compiler/crystal/semantic/abstract_def_checker.cr b/src/compiler/crystal/semantic/abstract_def_checker.cr index df2acb0b2fea..015285fed5cd 100644 --- a/src/compiler/crystal/semantic/abstract_def_checker.cr +++ b/src/compiler/crystal/semantic/abstract_def_checker.cr @@ -99,7 +99,11 @@ class Crystal::AbstractDefChecker type.defs.try &.each_value do |defs_with_metadata| defs_with_metadata.each do |def_with_metadata| a_def = def_with_metadata.def - return true if implements?(type, a_def, base, method) + + if implements?(type, a_def, base, method) + check_return_type(type, a_def, base, method) + return true + end end end false @@ -138,4 +142,22 @@ class Crystal::AbstractDefChecker true end + + def check_return_type(type : Type, method : Def, base_type : Type, base_method : Def) + base_return_type_node = base_method.return_type + return unless base_return_type_node + + base_return_type = base_type.lookup_type(base_return_type_node) + + return_type_node = method.return_type + unless return_type_node + method.raise "this method overrides #{Call.def_full_name(base_type, base_method)} which has an explicit return type of #{base_return_type}: please add an explicit return type to this method as well" + end + + return_type = type.lookup_type(return_type_node) + + unless return_type.implements?(base_return_type) + return_type_node.raise "this method must return #{base_return_type}, which is the return type of the overwritten method #{Call.def_full_name(base_type, base_method)}, not #{return_type}" + end + end end From 6bd319f620a39096150dcc6bbbacb4811b4dc420 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 9 Jul 2019 10:54:12 -0300 Subject: [PATCH 2/6] Compiler: skip abstract def check for generic types Let's tackle this in a future commit. It's tricky because of type parameters but it can be done. --- src/compiler/crystal/semantic/abstract_def_checker.cr | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/compiler/crystal/semantic/abstract_def_checker.cr b/src/compiler/crystal/semantic/abstract_def_checker.cr index 015285fed5cd..f486283efa4a 100644 --- a/src/compiler/crystal/semantic/abstract_def_checker.cr +++ b/src/compiler/crystal/semantic/abstract_def_checker.cr @@ -44,6 +44,10 @@ class Crystal::AbstractDefChecker @all_checked << type if type.abstract? || type.module? + # TODO: check generic types too + # (it's tricky because of type parameter resolution) + return if type.is_a?(GenericType) + type.defs.try &.each_value do |defs_with_metadata| defs_with_metadata.each do |def_with_metadata| a_def = def_with_metadata.def From 771bddce1b39830316f03136a1cf8245aa976523 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 9 Jul 2019 10:55:42 -0300 Subject: [PATCH 3/6] Add missing return types to match abstract defs --- spec/std/http/server/server_spec.cr | 2 +- spec/std/io/delimited_spec.cr | 2 +- spec/std/io/io_spec.cr | 2 +- spec/std/io/sized_spec.cr | 3 +-- src/flate/writer.cr | 2 +- src/http/content.cr | 4 ++-- src/http/server/response.cr | 2 +- src/http/web_socket/protocol.cr | 2 +- src/io/argf.cr | 2 +- src/io/buffered.cr | 2 +- src/io/delimited.cr | 2 +- src/io/hexdump.cr | 2 +- src/io/memory.cr | 2 +- src/io/multi_writer.cr | 2 +- src/io/sized.cr | 2 +- src/openssl/digest/digest_io.cr | 2 +- src/random.cr | 2 +- src/socket.cr | 2 +- src/socket/server.cr | 4 ++-- src/socket/tcp_server.cr | 2 +- src/string/builder.cr | 2 +- src/zip/checksum_reader.cr | 2 +- src/zip/checksum_writer.cr | 2 +- 23 files changed, 25 insertions(+), 26 deletions(-) diff --git a/spec/std/http/server/server_spec.cr b/spec/std/http/server/server_spec.cr index da6c2e47c697..6e4833fb8e4a 100644 --- a/spec/std/http/server/server_spec.cr +++ b/spec/std/http/server/server_spec.cr @@ -23,7 +23,7 @@ private class ReverseResponseOutput < IO def initialize(@output : IO) end - def write(slice : Bytes) + def write(slice : Bytes) : Nil slice.reverse_each do |byte| @output.write_byte(byte) end diff --git a/spec/std/io/delimited_spec.cr b/spec/std/io/delimited_spec.cr index 25884852f9a9..c88b4705fdc0 100644 --- a/spec/std/io/delimited_spec.cr +++ b/spec/std/io/delimited_spec.cr @@ -16,7 +16,7 @@ private class PartialReaderIO < IO read_size end - def write(slice : Bytes) + def write(slice : Bytes) : NoReturn raise "write" end end diff --git a/spec/std/io/io_spec.cr b/spec/std/io/io_spec.cr index 1855d6d3530b..1e29d01f809d 100644 --- a/spec/std/io/io_spec.cr +++ b/spec/std/io/io_spec.cr @@ -42,7 +42,7 @@ private class SimpleIOMemory < IO count end - def write(slice : Bytes) + def write(slice : Bytes) : Nil count = slice.size new_bytesize = bytesize + count if new_bytesize > @capacity diff --git a/spec/std/io/sized_spec.cr b/spec/std/io/sized_spec.cr index b5a9d7ee792c..8338063ff9db 100644 --- a/spec/std/io/sized_spec.cr +++ b/spec/std/io/sized_spec.cr @@ -5,8 +5,7 @@ private class NoPeekIO < IO 0 end - def write(bytes : Bytes) - 0 + def write(bytes : Bytes) : Nil end def peek diff --git a/src/flate/writer.cr b/src/flate/writer.cr index bfa99096954b..acea1a18bb5c 100644 --- a/src/flate/writer.cr +++ b/src/flate/writer.cr @@ -43,7 +43,7 @@ class Flate::Writer < IO end # See `IO#write`. - def write(slice : Bytes) + def write(slice : Bytes) : Nil check_open return if slice.empty? diff --git a/src/http/content.cr b/src/http/content.cr index 958bdf64a63a..00df7a9a0e31 100644 --- a/src/http/content.cr +++ b/src/http/content.cr @@ -78,7 +78,7 @@ module HTTP @io.skip(bytes_count) end - def write(slice : Bytes) + def write(slice : Bytes) : NoReturn raise IO::Error.new "Can't write to UnknownLengthContent" end end @@ -213,7 +213,7 @@ module HTTP end end - def write(slice : Bytes) + def write(slice : Bytes) : NoReturn raise IO::Error.new "Can't write to ChunkedContent" end diff --git a/src/http/server/response.cr b/src/http/server/response.cr index d30c030f92a0..efe850c11f4f 100644 --- a/src/http/server/response.cr +++ b/src/http/server/response.cr @@ -74,7 +74,7 @@ class HTTP::Server end # See `IO#write(slice)`. - def write(slice : Bytes) + def write(slice : Bytes) : Nil return if slice.empty? @output.write(slice) diff --git a/src/http/web_socket/protocol.cr b/src/http/web_socket/protocol.cr index 31383c54717a..dd06455389ba 100644 --- a/src/http/web_socket/protocol.cr +++ b/src/http/web_socket/protocol.cr @@ -49,7 +49,7 @@ class HTTP::WebSocket::Protocol @pos = 0 end - def write(slice : Bytes) + def write(slice : Bytes) : Nil return if slice.empty? count = Math.min(@buffer.size - @pos, slice.size) diff --git a/src/io/argf.cr b/src/io/argf.cr index f422ccd1a33f..341ceb6be7ac 100644 --- a/src/io/argf.cr +++ b/src/io/argf.cr @@ -54,7 +54,7 @@ class IO::ARGF < IO end end - def write(slice : Bytes) + def write(slice : Bytes) : NoReturn raise IO::Error.new "Can't write to ARGF" end diff --git a/src/io/buffered.cr b/src/io/buffered.cr index 45d79a5552a6..138b429e1ab3 100644 --- a/src/io/buffered.cr +++ b/src/io/buffered.cr @@ -124,7 +124,7 @@ module IO::Buffered end # Buffered implementation of `IO#write(slice)`. - def write(slice : Bytes) + def write(slice : Bytes) : Nil check_open return if slice.empty? diff --git a/src/io/delimited.cr b/src/io/delimited.cr index da0e0ca06fea..2142391502a0 100644 --- a/src/io/delimited.cr +++ b/src/io/delimited.cr @@ -107,7 +107,7 @@ class IO::Delimited < IO read_bytes end - def write(slice : Bytes) + def write(slice : Bytes) : Nil raise IO::Error.new "Can't write to IO::Delimited" end diff --git a/src/io/hexdump.cr b/src/io/hexdump.cr index a2f03d205880..f221423a8d57 100644 --- a/src/io/hexdump.cr +++ b/src/io/hexdump.cr @@ -32,7 +32,7 @@ class IO::Hexdump < IO end end - def write(buf : Bytes) + def write(buf : Bytes) : Nil return if buf.empty? @io.write(buf).tap do diff --git a/src/io/memory.cr b/src/io/memory.cr index 31085fa8b507..135955443471 100644 --- a/src/io/memory.cr +++ b/src/io/memory.cr @@ -82,7 +82,7 @@ class IO::Memory < IO # See `IO#write(slice)`. Raises if this `IO::Memory` is non-writeable, # or if it's non-resizeable and a resize is needed. - def write(slice : Bytes) + def write(slice : Bytes) : Nil check_writeable check_open diff --git a/src/io/multi_writer.cr b/src/io/multi_writer.cr index 78367e50be40..c06bc8649ed9 100644 --- a/src/io/multi_writer.cr +++ b/src/io/multi_writer.cr @@ -29,7 +29,7 @@ class IO::MultiWriter < IO @writers = writers.map(&.as(IO)).to_a end - def write(slice : Bytes) + def write(slice : Bytes) : Nil check_open return if slice.empty? diff --git a/src/io/sized.cr b/src/io/sized.cr index 72d98804ac06..253df834a743 100644 --- a/src/io/sized.cr +++ b/src/io/sized.cr @@ -72,7 +72,7 @@ class IO::Sized < IO end end - def write(slice : Bytes) + def write(slice : Bytes) : NoReturn raise IO::Error.new "Can't write to IO::Sized" end diff --git a/src/openssl/digest/digest_io.cr b/src/openssl/digest/digest_io.cr index 74c30a03165b..3be9a7c049c9 100644 --- a/src/openssl/digest/digest_io.cr +++ b/src/openssl/digest/digest_io.cr @@ -42,7 +42,7 @@ module OpenSSL read_bytes end - def write(slice : Bytes) + def write(slice : Bytes) : Nil return if slice.empty? if @mode.write? diff --git a/src/random.cr b/src/random.cr index af21faf1077a..d0f2b58fe1bb 100644 --- a/src/random.cr +++ b/src/random.cr @@ -66,7 +66,7 @@ module Random # # The integers must be uniformly distributed between `0` and # the maximal value for the chosen type. - abstract def next_u : UInt + abstract def next_u # Generates a random `Bool`. # diff --git a/src/socket.cr b/src/socket.cr index cb4fc09bb388..fb25d63ae647 100644 --- a/src/socket.cr +++ b/src/socket.cr @@ -217,7 +217,7 @@ class Socket < IO # socket.puts Time.utc # socket.close # ``` - def accept + def accept : Socket accept? || raise IO::Error.new("Closed stream") end diff --git a/src/socket/server.cr b/src/socket/server.cr index afba7855a766..8d282b4c56dd 100644 --- a/src/socket/server.cr +++ b/src/socket/server.cr @@ -14,7 +14,7 @@ class Socket # ``` # # If the server is closed after invoking this method, an `IO::Error` (closed stream) exception must be raised. - abstract def accept : Socket + abstract def accept : IO # Accepts an incoming connection and returns the client socket. # @@ -29,7 +29,7 @@ class Socket # socket.close # end # ``` - abstract def accept? : Socket? + abstract def accept? : IO? # Accepts an incoming connection and yields the client socket to the block. # Eventually closes the connection when the block returns. diff --git a/src/socket/tcp_server.cr b/src/socket/tcp_server.cr index 44a66f191d9e..73dc1ce086ff 100644 --- a/src/socket/tcp_server.cr +++ b/src/socket/tcp_server.cr @@ -103,7 +103,7 @@ class TCPServer < TCPSocket # end # end # ``` - def accept? + def accept? : TCPSocket? if client_fd = accept_impl sock = TCPSocket.new(fd: client_fd, family: family, type: type, protocol: protocol) sock.sync = sync? diff --git a/src/string/builder.cr b/src/string/builder.cr index 1370b1b5348c..c7dc2adf7867 100644 --- a/src/string/builder.cr +++ b/src/string/builder.cr @@ -38,7 +38,7 @@ class String::Builder < IO raise "Not implemented" end - def write(slice : Bytes) + def write(slice : Bytes) : Nil return if slice.empty? count = slice.size diff --git a/src/zip/checksum_reader.cr b/src/zip/checksum_reader.cr index 54b1538450f1..cd3ca613b688 100644 --- a/src/zip/checksum_reader.cr +++ b/src/zip/checksum_reader.cr @@ -24,7 +24,7 @@ module Zip @io.peek end - def write(slice : Bytes) + def write(slice : Bytes) : NoReturn raise IO::Error.new "Can't write to Zip::Reader or Zip::File entry" end end diff --git a/src/zip/checksum_writer.cr b/src/zip/checksum_writer.cr index 8866bc559123..9f65547a6268 100644 --- a/src/zip/checksum_writer.cr +++ b/src/zip/checksum_writer.cr @@ -13,7 +13,7 @@ module Zip raise IO::Error.new "Can't read from Zip::Writer entry" end - def write(slice : Bytes) + def write(slice : Bytes) : Nil return if slice.empty? @count += slice.size From 614a9bfcc3d950d112310382b321afdf270efbf5 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 9 Jul 2019 16:12:27 -0300 Subject: [PATCH 4/6] Compiler: check abstract defs for generic types --- spec/compiler/semantic/abstract_def_spec.cr | 109 +++++++++++++++++- .../crystal/semantic/abstract_def_checker.cr | 78 +++++++++++-- src/compiler/crystal/semantic/type_lookup.cr | 7 ++ 3 files changed, 185 insertions(+), 9 deletions(-) diff --git a/spec/compiler/semantic/abstract_def_spec.cr b/spec/compiler/semantic/abstract_def_spec.cr index 583b889897e3..78fb9693bf32 100644 --- a/spec/compiler/semantic/abstract_def_spec.cr +++ b/spec/compiler/semantic/abstract_def_spec.cr @@ -407,7 +407,7 @@ describe "Semantic: abstract def" do end end ), - "this method overrides Foo#foo() which has an explicit return type of Int32: please add an explicit return type to this method as well" + "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 @@ -425,7 +425,7 @@ describe "Semantic: abstract def" do end end ), - "this method must return Int32, which is the return type of the overwritten method Foo#foo(), not Bar::Int32" + "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 it "can return a more specific type" do @@ -450,4 +450,109 @@ describe "Semantic: abstract def" do Bar.new.foo )) { types["Child"] } end + + it "matches instantiated generic types" do + semantic(%( + abstract class Foo(T) + abstract def foo(x : T) + end + + abstract class Bar(U) < Foo(U) + end + + class Baz < Bar(Int32) + def foo(x : Int32) + end + end + )) + end + + it "matches generic types" do + semantic(%( + abstract class Foo(T) + abstract def foo(x : T) + end + + class Bar(U) < Foo(U) + def foo(x : U) + end + end + )) + end + + it "matches instantiated generic module" do + semantic(%( + module Foo(T) + abstract def foo(x : T) + end + + class Bar + include Foo(Int32) + + def foo(x : Int32) + end + end + )) + end + + it "matches generic module" do + semantic(%( + module Foo(T) + abstract def foo(x : T) + end + + class Bar(U) + include Foo(U) + + def foo(x : U) + end + end + )) + end + + it "matches generic module (a bit more complex)" do + semantic(%( + class Gen(T) + end + + module Foo(T) + abstract def foo(x : Gen(T)) + end + + class Bar + include Foo(Int32) + + def foo(x : Gen(Int32)) + end + end + )) + end + + it "matches generic return type" do + semantic(%( + abstract class Foo(T) + abstract def foo : T + end + + class Bar < Foo(Int32) + def foo : Int32 + 1 + end + end + )) + end + + it "is missing a return type in subclass of generic subclass" do + assert_error %( + abstract class Foo(T) + abstract def foo : T + end + + class Bar < Foo(Int32) + def foo + end + end + ), + "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 end diff --git a/src/compiler/crystal/semantic/abstract_def_checker.cr b/src/compiler/crystal/semantic/abstract_def_checker.cr index f486283efa4a..0f67ce19ab97 100644 --- a/src/compiler/crystal/semantic/abstract_def_checker.cr +++ b/src/compiler/crystal/semantic/abstract_def_checker.cr @@ -44,10 +44,6 @@ class Crystal::AbstractDefChecker @all_checked << type if type.abstract? || type.module? - # TODO: check generic types too - # (it's tricky because of type parameter resolution) - return if type.is_a?(GenericType) - type.defs.try &.each_value do |defs_with_metadata| defs_with_metadata.each do |def_with_metadata| a_def = def_with_metadata.def @@ -69,10 +65,11 @@ class Crystal::AbstractDefChecker end def check_implemented_in_subtypes(base, type, method) - # TODO: check generic modules subtypes = case type when NonGenericModuleType type.raw_including_types + when GenericModuleType + type.raw_including_types else type.subclasses end @@ -123,6 +120,16 @@ class Crystal::AbstractDefChecker return false if m1.args.size < m2.args.size + # If the base type is a generic type, we find the generic instantiation of + # t1 for it. This will have a mapping of type vars to types, for example + # T will be Int32 in something like `class Bar < Foo(Int32)` with `Foo(T)`. + # Then we replace all `T` in the base method with `Int32`, and just then + # we check if they match. + if t2.is_a?(GenericType) + generic_base = find_base_generic_instantiation(t1, t2) + m2 = replace_method_arg_paths_with_type_vars(t2, m2, generic_base) + end + m2.args.zip(m1.args) do |a2, a1| r1 = a1.restriction r2 = a2.restriction @@ -151,17 +158,74 @@ class Crystal::AbstractDefChecker base_return_type_node = base_method.return_type return unless base_return_type_node + original_base_return_type = base_type.lookup_type(base_return_type_node) + + # If the base type is a generic type, we find the generic instantiation of + # t1 for it. This will have a mapping of type vars to types, for example + # T will be Int32 in something like `class Bar < Foo(Int32)` with `Foo(T)`. + # Then we replace all `T` in the base method return type with `Int32`, + # and just then we check if they match. + if base_type.is_a?(GenericType) + generic_base = find_base_generic_instantiation(type, base_type) + + replacer = ReplacePathWithTypeVar.new(base_type, generic_base) + base_return_type_node = base_return_type_node.clone + base_return_type_node.accept(replacer) + end + base_return_type = base_type.lookup_type(base_return_type_node) return_type_node = method.return_type unless return_type_node - method.raise "this method overrides #{Call.def_full_name(base_type, base_method)} which has an explicit return type of #{base_return_type}: please add an explicit return type to this method as well" + method.raise "this method overrides #{Call.def_full_name(base_type, base_method)} which has an explicit return type of #{original_base_return_type}.\n#{@program.colorize("Please add an explicit return type (#{base_return_type} or a subtype of it) to this method as well.").yellow.bold}" end return_type = type.lookup_type(return_type_node) unless return_type.implements?(base_return_type) - return_type_node.raise "this method must return #{base_return_type}, which is the return type of the overwritten method #{Call.def_full_name(base_type, base_method)}, not #{return_type}" + return_type_node.raise "this method must return #{base_return_type}, which is the return type of the overridden method #{Call.def_full_name(base_type, base_method)}, or a subtype of it, not #{return_type}" + end + end + + def replace_method_arg_paths_with_type_vars(base_type : Type, method : Def, generic_type : GenericInstanceType) + replacer = ReplacePathWithTypeVar.new(base_type, generic_type) + + method = method.clone + method.args.each do |arg| + arg.restriction.try &.accept(replacer) + end + method + end + + def find_base_generic_instantiation(type : Type, base_type : GenericType) + type.ancestors.find do |t| + t.is_a?(GenericInstanceType) && t.generic_type == base_type + end.as(GenericInstanceType) + end + + class ReplacePathWithTypeVar < Visitor + def initialize(@base_type : GenericType, @generic_type : GenericInstanceType) + end + + def visit(node : Path) + if !node.global? && node.names.size == 1 + # Check if it matches any of the generic type vars + name = node.names.first + + type_var = @generic_type.type_vars[name]? + if type_var.is_a?(Var) + # Check that it's actually a type parameter on the base type + if @base_type.lookup_type?(node).is_a?(TypeParameter) + node.type = type_var.type + end + end + end + + false + end + + def visit(node : ASTNode) + true end end end diff --git a/src/compiler/crystal/semantic/type_lookup.cr b/src/compiler/crystal/semantic/type_lookup.cr index e30521ae7e27..b8b2cf71f79f 100644 --- a/src/compiler/crystal/semantic/type_lookup.cr +++ b/src/compiler/crystal/semantic/type_lookup.cr @@ -80,6 +80,13 @@ class Crystal::Type delegate program, to: @root def lookup(node : Path) + # A Path might have a type set. + # This is at least done in AbstractDefChecker when we replace type + # parameters with concrete types. + if type = node.type? + return type + end + type_var = lookup_type_var?(node) case type_var From 7d7972224337dc6c843ce7b2ddd90d04f13f754b Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 9 Jul 2019 16:12:41 -0300 Subject: [PATCH 5/6] Deque actually doesn't implement Comparable It could, but we can leave the implementation for later (or better: make an Indexable comparable to any other Indexable) --- src/deque.cr | 1 - 1 file changed, 1 deletion(-) diff --git a/src/deque.cr b/src/deque.cr index 9e79a0b0815d..986045847148 100644 --- a/src/deque.cr +++ b/src/deque.cr @@ -11,7 +11,6 @@ # [circular buffer](https://en.wikipedia.org/wiki/Circular_buffer). class Deque(T) include Indexable(T) - include Comparable(Deque) # This Deque is based on a circular buffer. It works like a normal array, but when an item is removed from the left # side, instead of shifting all the items, only the start position is shifted. This can lead to configurations like: From 9083fa9093b1e0dac524ca05b091fcaca3208500 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Tue, 9 Jul 2019 16:19:47 -0300 Subject: [PATCH 6/6] Add a flag to skip abstract def checking This is in case we got something wrong in the algorithm, so people can still compile their code, or they don't want to bother upgrading their code just yet. --- src/compiler/crystal/semantic.cr | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/compiler/crystal/semantic.cr b/src/compiler/crystal/semantic.cr index 531b77294d35..b7ac36231e59 100644 --- a/src/compiler/crystal/semantic.cr +++ b/src/compiler/crystal/semantic.cr @@ -69,9 +69,15 @@ class Crystal::Program node, processor = @progress_tracker.stage("Semantic (type declarations)") do TypeDeclarationProcessor.new(self).process(node) end - @progress_tracker.stage("Semantic (abstract def check)") do - AbstractDefChecker.new(self).run + + # TODO: remove this check a couple of versions after 0.30.0 once + # we are sure it's working fine for everyone + unless has_flag?("skip_abstract_def_check") + @progress_tracker.stage("Semantic (abstract def check)") do + AbstractDefChecker.new(self).run + end end + {node, processor} end end