From e33d88a93415ed6589244edc3e8574487614ead8 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 2 Apr 2025 13:09:48 +0100 Subject: [PATCH 1/5] [red-knot] Add GitHub PR annotations when mdtests fail in CI --- .github/workflows/ci.yaml | 50 ++++++++++++++++++ crates/red_knot_python_semantic/Cargo.toml | 5 ++ .../red_knot_python_semantic/tests/mdtest.rs | 8 +++ crates/red_knot_test/src/lib.rs | 51 +++++++++++++++---- 4 files changed, 104 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a8cdff50885e0..bc15c3a347de1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -36,6 +36,8 @@ jobs: code: ${{ steps.check_code.outputs.changed }} # Flag that is raised when any code that affects the fuzzer is changed fuzz: ${{ steps.check_fuzzer.outputs.changed }} + # Flag that is set to "true" when code related to red-knot changes. + red_knot: ${{ steps.check_red_knot.outputs.changed }} # Flag that is set to "true" when code related to the playground changes. playground: ${{ steps.check_playground.outputs.changed }} @@ -166,6 +168,29 @@ jobs: echo "changed=true" >> "$GITHUB_OUTPUT" fi + - name: Check if the red-knot code changed + id: check_red_knot + env: + MERGE_BASE: ${{ steps.merge_base.outputs.sha }} + run: | + if git diff --quiet "${MERGE_BASE}...HEAD" -- \ + ':Cargo.toml' \ + ':Cargo.lock' \ + ':crates/red_knot*/**' \ + ':crates/ruff_db/**' \ + ':crates/ruff_annotate_snippets/**' \ + ':crates/ruff_python_ast/**' \ + ':crates/ruff_python_parser/**' \ + ':crates/ruff_python_trivia/**' \ + ':crates/ruff_source_file/**' \ + ':crates/ruff_text_size/**' \ + ':.github/workflows/ci.yaml' \ + ; then + echo "changed=false" >> "$GITHUB_OUTPUT" + else + echo "changed=true" >> "$GITHUB_OUTPUT" + fi + cargo-fmt: name: "cargo fmt" runs-on: ubuntu-latest @@ -408,6 +433,31 @@ jobs: run: cargo binstall cargo-fuzz --force --disable-strategies crate-meta-data --no-confirm - run: cargo fuzz build -s none + mdtest-github-format: + name: "red-knot mdtest (GitHub annotations)" + runs-on: ubuntu-latest + needs: determine_changes + if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && needs.determine_changes.outputs.red_knot == 'true' && github.event_name == 'pull_request' }} + timeout-minutes: 10 + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + persist-credentials: false + - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2 + - name: "Install Rust toolchain" + run: rustup show + - name: "Install mold" + uses: rui314/setup-mold@v1 + - name: "Install cargo insta" + uses: taiki-e/install-action@6aca1cfa12ef3a6b98ee8c70e0171bfa067604f5 # v2 + with: + tool: cargo-insta + - name: "Run mdtest" + shell: bash + env: + NO_COLOR: 1 + run: cargo test --features=mdtest_github_output_format -p red_knot_python_semantic --test mdtest + fuzz-parser: name: "fuzz parser" runs-on: ubuntu-latest diff --git a/crates/red_knot_python_semantic/Cargo.toml b/crates/red_knot_python_semantic/Cargo.toml index c1ef36cb14629..1a244496f2472 100644 --- a/crates/red_knot_python_semantic/Cargo.toml +++ b/crates/red_knot_python_semantic/Cargo.toml @@ -61,5 +61,10 @@ quickcheck_macros = { version = "1.0.0" } [features] serde = ["ruff_db/serde", "dep:serde", "ruff_python_ast/serde"] +# When enabled, this feature causes mdtest to output failures in a format +# that leads GitHub Actions to annotate the PR diff with the failures. +# (Nice for CI, not so nice for local development.) +mdtest_github_output_format = [] + [lints] workspace = true diff --git a/crates/red_knot_python_semantic/tests/mdtest.rs b/crates/red_knot_python_semantic/tests/mdtest.rs index 9c21cc51b2c72..54febb7062d09 100644 --- a/crates/red_knot_python_semantic/tests/mdtest.rs +++ b/crates/red_knot_python_semantic/tests/mdtest.rs @@ -1,5 +1,6 @@ use camino::Utf8Path; use dir_test::{dir_test, Fixture}; +use red_knot_test::OutputFormat; /// See `crates/red_knot_test/README.md` for documentation on these tests. #[dir_test( @@ -18,12 +19,19 @@ fn mdtest(fixture: Fixture<&str>) { let test_name = test_name("mdtest", absolute_fixture_path); + let output_format = if cfg!(feature = "mdtest_github_output_format") { + OutputFormat::GitHub + } else { + OutputFormat::Cargo + }; + red_knot_test::run( absolute_fixture_path, relative_fixture_path, &snapshot_path, short_title, &test_name, + output_format, ); } diff --git a/crates/red_knot_test/src/lib.rs b/crates/red_knot_test/src/lib.rs index ec95d14f0e4b1..484573c8c4b6f 100644 --- a/crates/red_knot_test/src/lib.rs +++ b/crates/red_knot_test/src/lib.rs @@ -34,6 +34,7 @@ pub fn run( snapshot_path: &Utf8Path, short_title: &str, test_name: &str, + output_format: OutputFormat, ) { let source = std::fs::read_to_string(absolute_fixture_path).unwrap(); let suite = match test_parser::parse(short_title, &source) { @@ -59,7 +60,10 @@ pub fn run( if let Err(failures) = run_test(&mut db, relative_fixture_path, snapshot_path, &test) { any_failures = true; - println!("\n{}\n", test.name().bold().underline()); + + if output_format.is_cargo() { + println!("\n{}\n", test.name().bold().underline()); + } let md_index = LineIndex::from_source_text(&source); @@ -72,21 +76,31 @@ pub fn run( source_map.to_absolute_line_number(relative_line_number); for failure in failures { - let line_info = - format!("{relative_fixture_path}:{absolute_line_number}").cyan(); - println!(" {line_info} {failure}"); + match output_format { + OutputFormat::Cargo => { + let line_info = + format!("{relative_fixture_path}:{absolute_line_number}") + .cyan(); + println!(" {line_info} {failure}"); + } + OutputFormat::GitHub => println!( + "::error file={absolute_fixture_path},line={absolute_line_number}::{failure}" + ), + } } } } let escaped_test_name = test.name().replace('\'', "\\'"); - println!( - "\nTo rerun this specific test, set the environment variable: {MDTEST_TEST_FILTER}='{escaped_test_name}'", - ); - println!( - "{MDTEST_TEST_FILTER}='{escaped_test_name}' cargo test -p red_knot_python_semantic --test mdtest -- {test_name}", - ); + if output_format.is_cargo() { + println!( + "\nTo rerun this specific test, set the environment variable: {MDTEST_TEST_FILTER}='{escaped_test_name}'", + ); + println!( + "{MDTEST_TEST_FILTER}='{escaped_test_name}' cargo test -p red_knot_python_semantic --test mdtest -- {test_name}", + ); + } } } @@ -95,6 +109,23 @@ pub fn run( assert!(!any_failures, "Some tests failed."); } +/// Defines the format in which mdtest should print an error to the terminal +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum OutputFormat { + /// The format `cargo test` should use by default. + Cargo, + /// A format that will provide annotations from GitHub Actions + /// if mdtest fails on a PR. + /// See + GitHub, +} + +impl OutputFormat { + const fn is_cargo(self) -> bool { + matches!(self, OutputFormat::Cargo) + } +} + fn run_test( db: &mut db::Db, relative_fixture_path: &Utf8Path, From ff284161dd3712f968d63ff791f86b58b5293286 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 2 Apr 2025 14:18:09 +0100 Subject: [PATCH 2/5] switch to environment variable and rename enum variant --- .github/workflows/ci.yaml | 3 ++- crates/red_knot_python_semantic/Cargo.toml | 5 ----- crates/red_knot_python_semantic/tests/mdtest.rs | 4 ++-- crates/red_knot_test/src/lib.rs | 12 ++++++------ 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index bc15c3a347de1..b398db854231e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -456,7 +456,8 @@ jobs: shell: bash env: NO_COLOR: 1 - run: cargo test --features=mdtest_github_output_format -p red_knot_python_semantic --test mdtest + MDTEST_GITHUB_ANNOTATIONS_FORMAT: 1 + run: cargo test -p red_knot_python_semantic --test mdtest fuzz-parser: name: "fuzz parser" diff --git a/crates/red_knot_python_semantic/Cargo.toml b/crates/red_knot_python_semantic/Cargo.toml index 1a244496f2472..c1ef36cb14629 100644 --- a/crates/red_knot_python_semantic/Cargo.toml +++ b/crates/red_knot_python_semantic/Cargo.toml @@ -61,10 +61,5 @@ quickcheck_macros = { version = "1.0.0" } [features] serde = ["ruff_db/serde", "dep:serde", "ruff_python_ast/serde"] -# When enabled, this feature causes mdtest to output failures in a format -# that leads GitHub Actions to annotate the PR diff with the failures. -# (Nice for CI, not so nice for local development.) -mdtest_github_output_format = [] - [lints] workspace = true diff --git a/crates/red_knot_python_semantic/tests/mdtest.rs b/crates/red_knot_python_semantic/tests/mdtest.rs index 54febb7062d09..b2ea0f141c268 100644 --- a/crates/red_knot_python_semantic/tests/mdtest.rs +++ b/crates/red_knot_python_semantic/tests/mdtest.rs @@ -19,10 +19,10 @@ fn mdtest(fixture: Fixture<&str>) { let test_name = test_name("mdtest", absolute_fixture_path); - let output_format = if cfg!(feature = "mdtest_github_output_format") { + let output_format = if std::env::var("MDTEST_GITHUB_ANNOTATIONS_FORMAT").is_ok() { OutputFormat::GitHub } else { - OutputFormat::Cargo + OutputFormat::Cli }; red_knot_test::run( diff --git a/crates/red_knot_test/src/lib.rs b/crates/red_knot_test/src/lib.rs index 484573c8c4b6f..38cb5b0c70f41 100644 --- a/crates/red_knot_test/src/lib.rs +++ b/crates/red_knot_test/src/lib.rs @@ -61,7 +61,7 @@ pub fn run( if let Err(failures) = run_test(&mut db, relative_fixture_path, snapshot_path, &test) { any_failures = true; - if output_format.is_cargo() { + if output_format.is_cli() { println!("\n{}\n", test.name().bold().underline()); } @@ -77,7 +77,7 @@ pub fn run( for failure in failures { match output_format { - OutputFormat::Cargo => { + OutputFormat::Cli => { let line_info = format!("{relative_fixture_path}:{absolute_line_number}") .cyan(); @@ -93,7 +93,7 @@ pub fn run( let escaped_test_name = test.name().replace('\'', "\\'"); - if output_format.is_cargo() { + if output_format.is_cli() { println!( "\nTo rerun this specific test, set the environment variable: {MDTEST_TEST_FILTER}='{escaped_test_name}'", ); @@ -113,7 +113,7 @@ pub fn run( #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum OutputFormat { /// The format `cargo test` should use by default. - Cargo, + Cli, /// A format that will provide annotations from GitHub Actions /// if mdtest fails on a PR. /// See @@ -121,8 +121,8 @@ pub enum OutputFormat { } impl OutputFormat { - const fn is_cargo(self) -> bool { - matches!(self, OutputFormat::Cargo) + const fn is_cli(self) -> bool { + matches!(self, OutputFormat::Cli) } } From 494ef81cd8b2edf9626f4b3f69fd759580c92c1e Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 2 Apr 2025 16:20:16 +0100 Subject: [PATCH 3/5] try to cache the test binaries in CI and reuse them between jobs --- .github/workflows/ci.yaml | 48 +++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b398db854231e..bb2ff11767f2c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -223,11 +223,37 @@ jobs: - name: "Clippy (wasm)" run: cargo clippy -p ruff_wasm -p red_knot_wasm --target wasm32-unknown-unknown --all-features --locked -- -D warnings - cargo-test-linux: - name: "cargo test (linux)" + build-linux: + name: "build on Linux" runs-on: depot-ubuntu-22.04-16 needs: determine_changes if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }} + timeout-minutes: 10 + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + persist-credentials: false + - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2 + - name: "Install Rust toolchain" + run: rustup show + - name: "Install mold" + uses: rui314/setup-mold@v1 + - name: "Build" + run: cargo test --locked --no-run + # Tarring the binaries ensures executable permissions are retained + # when the binaries are downloaded again + - name: "Tar binaries" + run: tar -cvf test-binaries.tar target/debug/deps + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: test-binaries + path: test-binaries.tar + + cargo-test-linux: + name: "cargo test (linux)" + runs-on: depot-ubuntu-22.04-16 + needs: build-linux + if: ${{ needs.build-linux.result == 'success' }} timeout-minutes: 20 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 @@ -246,6 +272,11 @@ jobs: uses: taiki-e/install-action@6aca1cfa12ef3a6b98ee8c70e0171bfa067604f5 # v2 with: tool: cargo-insta + - name: "Download cached binaries for tests" + uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + id: download-cached-binaries + with: + name: test-binaries - name: "Run tests" shell: bash env: @@ -436,8 +467,10 @@ jobs: mdtest-github-format: name: "red-knot mdtest (GitHub annotations)" runs-on: ubuntu-latest - needs: determine_changes - if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && needs.determine_changes.outputs.red_knot == 'true' && github.event_name == 'pull_request' }} + needs: + - determine_changes + - build-linux + if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && needs.determine_changes.outputs.red_knot == 'true' && github.event_name == 'pull_request' && needs.build-linux.result == 'success' }} timeout-minutes: 10 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 @@ -448,10 +481,11 @@ jobs: run: rustup show - name: "Install mold" uses: rui314/setup-mold@v1 - - name: "Install cargo insta" - uses: taiki-e/install-action@6aca1cfa12ef3a6b98ee8c70e0171bfa067604f5 # v2 + - name: "Download cached binaries for tests" + uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + id: download-cached-binaries with: - tool: cargo-insta + name: test-binaries - name: "Run mdtest" shell: bash env: From 0f242d5e22aa6f38994770d1276da0185c55cfc2 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 2 Apr 2025 18:22:40 +0100 Subject: [PATCH 4/5] try another idea from Micha --- .github/workflows/ci.yaml | 70 +++---------------- .../type_properties/is_equivalent_to.md | 4 +- 2 files changed, 10 insertions(+), 64 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index bb2ff11767f2c..455c76364d157 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -223,37 +223,11 @@ jobs: - name: "Clippy (wasm)" run: cargo clippy -p ruff_wasm -p red_knot_wasm --target wasm32-unknown-unknown --all-features --locked -- -D warnings - build-linux: - name: "build on Linux" - runs-on: depot-ubuntu-22.04-16 - needs: determine_changes - if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }} - timeout-minutes: 10 - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - with: - persist-credentials: false - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2 - - name: "Install Rust toolchain" - run: rustup show - - name: "Install mold" - uses: rui314/setup-mold@v1 - - name: "Build" - run: cargo test --locked --no-run - # Tarring the binaries ensures executable permissions are retained - # when the binaries are downloaded again - - name: "Tar binaries" - run: tar -cvf test-binaries.tar target/debug/deps - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: test-binaries - path: test-binaries.tar - cargo-test-linux: name: "cargo test (linux)" runs-on: depot-ubuntu-22.04-16 - needs: build-linux - if: ${{ needs.build-linux.result == 'success' }} + needs: determine_changes + if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }} timeout-minutes: 20 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 @@ -272,11 +246,12 @@ jobs: uses: taiki-e/install-action@6aca1cfa12ef3a6b98ee8c70e0171bfa067604f5 # v2 with: tool: cargo-insta - - name: "Download cached binaries for tests" - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 - id: download-cached-binaries - with: - name: test-binaries + - name: Red-knot mdtests (GitHub annotations) + if: ${{ needs.determine_changes.outputs.red_knot == 'true' }} + env: + NO_COLOR: 1 + MDTEST_GITHUB_ANNOTATIONS_FORMAT: 1 + run: cargo test -p red_knot_python_semantic --test mdtest || true - name: "Run tests" shell: bash env: @@ -464,35 +439,6 @@ jobs: run: cargo binstall cargo-fuzz --force --disable-strategies crate-meta-data --no-confirm - run: cargo fuzz build -s none - mdtest-github-format: - name: "red-knot mdtest (GitHub annotations)" - runs-on: ubuntu-latest - needs: - - determine_changes - - build-linux - if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && needs.determine_changes.outputs.red_knot == 'true' && github.event_name == 'pull_request' && needs.build-linux.result == 'success' }} - timeout-minutes: 10 - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - with: - persist-credentials: false - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2 - - name: "Install Rust toolchain" - run: rustup show - - name: "Install mold" - uses: rui314/setup-mold@v1 - - name: "Download cached binaries for tests" - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 - id: download-cached-binaries - with: - name: test-binaries - - name: "Run mdtest" - shell: bash - env: - NO_COLOR: 1 - MDTEST_GITHUB_ANNOTATIONS_FORMAT: 1 - run: cargo test -p red_knot_python_semantic --test mdtest - fuzz-parser: name: "fuzz parser" runs-on: ubuntu-latest diff --git a/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md b/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md index b2606cf4347c7..29a4e5fc29723 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md +++ b/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md @@ -213,7 +213,7 @@ When the parameter kinds are different: def f7(a: int, /) -> None: ... def f8(a: int) -> None: ... -static_assert(not is_equivalent_to(CallableTypeOf[f7], CallableTypeOf[f8])) +static_assert(not is_equivalent_to(CallableTypeOf[f7], CallableTypeOf[f8])) # revealed: int ``` When the annotated types of the parameters are not equivalent or absent in one or both of the @@ -224,7 +224,7 @@ def f9(a: int) -> None: ... def f10(a: str) -> None: ... def f11(a) -> None: ... -static_assert(not is_equivalent_to(CallableTypeOf[f9], CallableTypeOf[f10])) +static_assert(is_equivalent_to(CallableTypeOf[f9], CallableTypeOf[f10])) static_assert(not is_equivalent_to(CallableTypeOf[f10], CallableTypeOf[f11])) static_assert(not is_equivalent_to(CallableTypeOf[f11], CallableTypeOf[f10])) static_assert(not is_equivalent_to(CallableTypeOf[f11], CallableTypeOf[f11])) From 3fd7de9a8666bac04d8578425499469868b136ff Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 2 Apr 2025 18:29:00 +0100 Subject: [PATCH 5/5] remove deliberate mdtest failures, add comment --- .github/workflows/ci.yaml | 2 ++ .../resources/mdtest/type_properties/is_equivalent_to.md | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 455c76364d157..58ee061cedb43 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -251,6 +251,8 @@ jobs: env: NO_COLOR: 1 MDTEST_GITHUB_ANNOTATIONS_FORMAT: 1 + # Ignore errors if this step fails; we want to continue to later steps in the workflow anyway. + # This step is just to get nice GitHub annotations on the PR diff in the files-changed tab. run: cargo test -p red_knot_python_semantic --test mdtest || true - name: "Run tests" shell: bash diff --git a/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md b/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md index 29a4e5fc29723..b2606cf4347c7 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md +++ b/crates/red_knot_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md @@ -213,7 +213,7 @@ When the parameter kinds are different: def f7(a: int, /) -> None: ... def f8(a: int) -> None: ... -static_assert(not is_equivalent_to(CallableTypeOf[f7], CallableTypeOf[f8])) # revealed: int +static_assert(not is_equivalent_to(CallableTypeOf[f7], CallableTypeOf[f8])) ``` When the annotated types of the parameters are not equivalent or absent in one or both of the @@ -224,7 +224,7 @@ def f9(a: int) -> None: ... def f10(a: str) -> None: ... def f11(a) -> None: ... -static_assert(is_equivalent_to(CallableTypeOf[f9], CallableTypeOf[f10])) +static_assert(not is_equivalent_to(CallableTypeOf[f9], CallableTypeOf[f10])) static_assert(not is_equivalent_to(CallableTypeOf[f10], CallableTypeOf[f11])) static_assert(not is_equivalent_to(CallableTypeOf[f11], CallableTypeOf[f10])) static_assert(not is_equivalent_to(CallableTypeOf[f11], CallableTypeOf[f11]))