Dialyzer 不会捕获返回函数的错误
Dialyzer does not catch errors on returned functions
背景
在使用 dialyzer、typespecs 和 currying 时,我能够在 dialyzer 中创建一个误报示例。
为了这个 MWE, I am using diallyxir(包括版本)的目的,因为它让我的生活更轻松。 dialyxir 的作者确认这不是他们方面的问题,因此暂时排除这种可能性。
环境
$ elixir -v
Erlang/OTP 24 [erts-12.2.1] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [jit]
Elixir 1.13.2 (compiled with Erlang/OTP 24)
- 您使用的是哪个版本的 Dialyxir? (cat mix.lock | grep dialyxir):
"dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
当前行为
给定以下代码示例:
defmodule PracticingCurrying do
@spec greater_than(integer()) :: (integer() -> String.t())
def greater_than(min) do
fn number -> number > min end
end
end
明显是打错了,我收到一条成功信息:
$ mix dialyzer
Compiling 1 file (.ex)
Generated grokking_fp app
Finding suitable PLTs
Checking PLT...
[:compiler, :currying, :elixir, :gradient, :gradualizer, :kernel, :logger, :stdlib, :syntax_tools]
Looking up modules in dialyxir_erlang-24.2.1_elixir-1.13.2_deps-dev.plt
Finding applications for dialyxir_erlang-24.2.1_elixir-1.13.2_deps-dev.plt
Finding modules for dialyxir_erlang-24.2.1_elixir-1.13.2_deps-dev.plt
Checking 518 modules in dialyxir_erlang-24.2.1_elixir-1.13.2_deps-dev.plt
Adding 44 modules to dialyxir_erlang-24.2.1_elixir-1.13.2_deps-dev.plt
done in 0m24.18s
No :ignore_warnings opt specified in mix.exs and default does not exist.
Starting Dialyzer
[
check_plt: false,
init_plt: '/home/user/Workplace/fl4m3/grokking_fp/_build/dev/dialyxir_erlang-24.2.1_elixir-1.13.2_deps-dev.plt',
files: ['/home/user/Workplace/fl4m3/grokking_fp/_build/dev/lib/grokking_fp/ebin/Elixir.ImmutableValues.beam',
'/home/user/Workplace/fl4m3/grokking_fp/_build/dev/lib/grokking_fp/ebin/Elixir.PracticingCurrying.beam',
'/home/user/Workplace/fl4m3/grokking_fp/_build/dev/lib/grokking_fp/ebin/Elixir.TipCalculator.beam'],
warnings: [:unknown]
]
Total errors: 0, Skipped: 0, Unnecessary Skips: 0
done in 0m1.02s
done (passed successfully)
预期行为
我希望透析器告诉我正确的规格是 @spec greater_than(integer()) :: (integer() -> bool())
。
作为旁注(和比较,如果你愿意的话)gradient 确实发现了错误。
我知道比较这些工具就像比较橙子和苹果,但我认为还是值得一提。
问题
- dialyzer 不是为了捕捉这种类型的错误吗?
- 如果它应该捕捉到错误,可能是什么失败了? (是我的示例不正确,还是透析器内部有问题?)
我个人很难相信这可能是 Dialyzer 中的一个错误,这个工具已经被很多人广泛使用,我是第一个发现这个错误的人。但是,我无法解释发生了什么。
感谢帮助。
Dialyzer 在其分析中相当乐观,并忽略了某些类别的错误。
This article 对其方法和限制提供了一些高级解释。
在匿名函数的特殊情况下,dialyzer 似乎执行了非常少的检查
声明它们时:它将忽略其参数的类型和 return 类型,例如
以下即使明显错误也不会导致任何错误:
# no error
@spec add(integer()) :: (String.t() -> String.t())
def add(x) do
fn y -> x + y end
end
然而它会指出数量不匹配,例如
# invalid_contract
# The @spec for the function does not match the success typing of the function.
@spec add2(integer()) :: (integer(), integer() -> integer())
def add2(x) do
fn y -> x + y end
end
Dialyzer 在尝试使用匿名函数时可能会检测到类型冲突,
但这并不能保证(请参阅上面的文章),并且错误消息可能没有帮助:
# Function main/0 has no local return.
def main do
positive? = greater_than(0)
positive?.(2)
end
我们不知道到底是什么问题,甚至不知道导致错误的行。但至少我们知道有一个并且可以调试它。
在下面的示例中,错误信息更多(使用 :lists.map/2
而不是 Enum.map/2
因为
dialyzer 不理解可枚举协议):
# Function main2/0 has no local return.
def main2 do
positive? = greater_than(0)
# The function call will not succeed.
# :lists.map(_positive? :: (integer() -> none()), [-2 | 0 | 1, ...])
# will never return since the success typing arguments are
# ((_ -> any()), [any()])
:lists.map(positive?, [1, 0, -2])
end
这告诉我们透析器将 greater_than/1
的 return 类型推断为 (integer() -> none())
。
none
在上面的文章中描述为:
This is a special type that means that no term or type is valid.
Usually, when Dialyzer boils down the possible return values of a function to none()
, it means the function should crash.
It is synonymous with "this stuff won't work."
所以dialyzer知道这个函数不能调用成功,但是直到真正调用时才认为它是类型冲突,所以它会允许声明(同样你可以完美地创建一个函数只是raise
s).
免责声明:我找不到关于透析器如何处理匿名的官方解释
详细功能,所以上面的解释是基于我的观察和解释
背景
在使用 dialyzer、typespecs 和 currying 时,我能够在 dialyzer 中创建一个误报示例。
为了这个 MWE, I am using diallyxir(包括版本)的目的,因为它让我的生活更轻松。 dialyxir 的作者确认这不是他们方面的问题,因此暂时排除这种可能性。
环境
$ elixir -v
Erlang/OTP 24 [erts-12.2.1] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [jit]
Elixir 1.13.2 (compiled with Erlang/OTP 24)
- 您使用的是哪个版本的 Dialyxir? (cat mix.lock | grep dialyxir):
"dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
当前行为
给定以下代码示例:
defmodule PracticingCurrying do
@spec greater_than(integer()) :: (integer() -> String.t())
def greater_than(min) do
fn number -> number > min end
end
end
明显是打错了,我收到一条成功信息:
$ mix dialyzer
Compiling 1 file (.ex)
Generated grokking_fp app
Finding suitable PLTs
Checking PLT...
[:compiler, :currying, :elixir, :gradient, :gradualizer, :kernel, :logger, :stdlib, :syntax_tools]
Looking up modules in dialyxir_erlang-24.2.1_elixir-1.13.2_deps-dev.plt
Finding applications for dialyxir_erlang-24.2.1_elixir-1.13.2_deps-dev.plt
Finding modules for dialyxir_erlang-24.2.1_elixir-1.13.2_deps-dev.plt
Checking 518 modules in dialyxir_erlang-24.2.1_elixir-1.13.2_deps-dev.plt
Adding 44 modules to dialyxir_erlang-24.2.1_elixir-1.13.2_deps-dev.plt
done in 0m24.18s
No :ignore_warnings opt specified in mix.exs and default does not exist.
Starting Dialyzer
[
check_plt: false,
init_plt: '/home/user/Workplace/fl4m3/grokking_fp/_build/dev/dialyxir_erlang-24.2.1_elixir-1.13.2_deps-dev.plt',
files: ['/home/user/Workplace/fl4m3/grokking_fp/_build/dev/lib/grokking_fp/ebin/Elixir.ImmutableValues.beam',
'/home/user/Workplace/fl4m3/grokking_fp/_build/dev/lib/grokking_fp/ebin/Elixir.PracticingCurrying.beam',
'/home/user/Workplace/fl4m3/grokking_fp/_build/dev/lib/grokking_fp/ebin/Elixir.TipCalculator.beam'],
warnings: [:unknown]
]
Total errors: 0, Skipped: 0, Unnecessary Skips: 0
done in 0m1.02s
done (passed successfully)
预期行为
我希望透析器告诉我正确的规格是 @spec greater_than(integer()) :: (integer() -> bool())
。
作为旁注(和比较,如果你愿意的话)gradient 确实发现了错误。 我知道比较这些工具就像比较橙子和苹果,但我认为还是值得一提。
问题
- dialyzer 不是为了捕捉这种类型的错误吗?
- 如果它应该捕捉到错误,可能是什么失败了? (是我的示例不正确,还是透析器内部有问题?)
我个人很难相信这可能是 Dialyzer 中的一个错误,这个工具已经被很多人广泛使用,我是第一个发现这个错误的人。但是,我无法解释发生了什么。
感谢帮助。
Dialyzer 在其分析中相当乐观,并忽略了某些类别的错误。 This article 对其方法和限制提供了一些高级解释。
在匿名函数的特殊情况下,dialyzer 似乎执行了非常少的检查 声明它们时:它将忽略其参数的类型和 return 类型,例如 以下即使明显错误也不会导致任何错误:
# no error
@spec add(integer()) :: (String.t() -> String.t())
def add(x) do
fn y -> x + y end
end
然而它会指出数量不匹配,例如
# invalid_contract
# The @spec for the function does not match the success typing of the function.
@spec add2(integer()) :: (integer(), integer() -> integer())
def add2(x) do
fn y -> x + y end
end
Dialyzer 在尝试使用匿名函数时可能会检测到类型冲突, 但这并不能保证(请参阅上面的文章),并且错误消息可能没有帮助:
# Function main/0 has no local return.
def main do
positive? = greater_than(0)
positive?.(2)
end
我们不知道到底是什么问题,甚至不知道导致错误的行。但至少我们知道有一个并且可以调试它。
在下面的示例中,错误信息更多(使用 :lists.map/2
而不是 Enum.map/2
因为
dialyzer 不理解可枚举协议):
# Function main2/0 has no local return.
def main2 do
positive? = greater_than(0)
# The function call will not succeed.
# :lists.map(_positive? :: (integer() -> none()), [-2 | 0 | 1, ...])
# will never return since the success typing arguments are
# ((_ -> any()), [any()])
:lists.map(positive?, [1, 0, -2])
end
这告诉我们透析器将 greater_than/1
的 return 类型推断为 (integer() -> none())
。
none
在上面的文章中描述为:
This is a special type that means that no term or type is valid. Usually, when Dialyzer boils down the possible return values of a function to
none()
, it means the function should crash. It is synonymous with "this stuff won't work."
所以dialyzer知道这个函数不能调用成功,但是直到真正调用时才认为它是类型冲突,所以它会允许声明(同样你可以完美地创建一个函数只是raise
s).
免责声明:我找不到关于透析器如何处理匿名的官方解释 详细功能,所以上面的解释是基于我的观察和解释