diff --git a/.editorconfig b/.editorconfig index 4cab270c3..3ba6837eb 100644 --- a/.editorconfig +++ b/.editorconfig @@ -30,6 +30,8 @@ indent_size = 2 # Dotnet code style settings: [*.{cs,vb}] +tab_width = 4 + # Sort using and Import directives with System.* appearing first dotnet_sort_system_directives_first = true # Avoid "this." and "Me." if not necessary @@ -57,6 +59,9 @@ dotnet_style_require_accessibility_modifiers = omit_if_default:error # IDE0040: Add accessibility modifiers dotnet_diagnostic.IDE0040.severity = error +# IDE1100: Error reading content of source file 'Project.TargetFrameworkMoniker' (i.e. from ThisAssembly) +dotnet_diagnostic.IDE1100.severity = none + [*.cs] # Top-level files are definitely OK csharp_using_directive_placement = outside_namespace:silent diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 000000000..514ebd533 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,94 @@ +# .NET Repository + +**Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.** + +## Working Effectively + +### Essential Build Commands +- **Restore dependencies**: `dotnet restore` + +- **Build the entire solution**: `dotnet build` + +- **Run tests**: `dnx --yes retest` + - Runs all unit tests across the solution + - If tests fail due to Azure Storage, run the following commands and retry: `npm install azurite` and `npx azurite &` + +### Build Validation and CI Requirements +- **Always run before committing**: + * `dnx --yes retest` + * `dotnet format whitespace -v:diag --exclude ~/.nuget` + * `dotnet format style -v:diag --exclude ~/.nuget` + +### Project Structure and Navigation + +| Directory | Description | +|-----------|-------------| +| `src/` | Contains the repo source code. | +| `bin/` | Contains built packages (if any) | + +### Code Style and Formatting + +#### EditorConfig Rules +The repository uses `.editorconfig` at the repo root for consistent code style. + +- **Indentation**: 4 spaces for C# files, 2 spaces for XML/YAML/JSON +- **Line endings**: LF (Unix-style) +- **Sort using directives**: System.* namespaces first (`dotnet_sort_system_directives_first = true`) +- **Type references**: Prefer language keywords over framework type names (`int` vs `Int32`) +- **Modern C# features**: Use object/collection initializers, coalesce expressions when possible, use var when the type is apparent from the right-hand side of the assignment +- **Visibility modifiers**: only explicitly specify visibility when different from the default (e.g. `public` for classes, no `internal` for classes or `private` for fields, etc.) + +#### Formatting Validation +- CI enforces formatting with `dotnet format whitespace` and `dotnet format style` +- Run locally: `dotnet format whitespace --verify-no-changes -v:diag --exclude ~/.nuget` +- Fix formatting: `dotnet format` (without `--verify-no-changes`) + +### Testing Practices + +#### Test Framework +- **xUnit** for all unit and integration tests +- **Moq** for mocking dependencies +- Located in `src/*.Tests/` + +#### Test Attributes +Custom xUnit attributes are sometimes used for conditional test execution: +- `[SecretsFact("XAI_API_KEY")]` - Skips test if required secrets are missing from user secrets or environment variables +- `[LocalFact("SECRET")]` - Runs only locally (skips in CI), requires specified secrets +- `[CIFact]` - Runs only in CI environment + +### Dependency Management + +#### Adding Dependencies +- Add to appropriate `.csproj` file +- Run `dotnet restore` to update dependencies +- Ensure version consistency across projects where applicable + +#### CI/CD Pipeline +- **Build workflow**: `.github/workflows/build.yml` - runs on PR and push to main/rel/feature branches +- **Publish workflow**: Publishes to Sleet feed when `SLEET_CONNECTION` secret is available +- **OS matrix**: Configured in `.github/workflows/os-matrix.json` (defaults to ubuntu-latest) + +### Special Files and Tools + +#### dnx Command +- **Purpose**: built-in tool for running arbitrary dotnet tools that are published on nuget.org. `--yes` auto-confirms install before run. +- **Example**: `dnx --yes retest` - runs tests with automatic retry on transient failures (retest being a tool package published at https://www.nuget.org/packages/retest) +- **In CI**: `dnx --yes retest -- --no-build` (skips build, runs tests only) + +#### Directory.Build.rsp +- MSBuild response file with default build arguments +- `-nr:false` - disables node reuse +- `-m:1` - single-threaded build (for stability) +- `-v:m` - minimal verbosity + +#### Code Quality +- All PRs must pass format validation +- Tests must pass on all target frameworks +- Follow existing patterns and conventions in the codebase + +## Documenting Work + +Project implemention details, design and key decisions should be documented in a top-level AGENTS.md file at the repo root. +Keep this file updated whenever you make change significant changes for future reference. + +User-facing features and APIs should be documented to highlight (not extensively, as an overview) key project features and capabilities, in the readme.md file at the repo root. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 949c4328a..17ca3e260 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -24,9 +24,17 @@ updates: Extensions: patterns: - "Microsoft.Extensions*" + exclude-patterns: + - "Microsoft.Extensions.AI*" + ExtensionsAI: + patterns: + - "Microsoft.Extensions.AI*" Web: patterns: - "Microsoft.AspNetCore*" + OpenTelemetry: + patterns: + - "OpenTelemetry*" Tests: patterns: - "Microsoft.NET.Test*" diff --git a/.github/dotnet.json b/.github/dotnet.json new file mode 100644 index 000000000..a1e2be263 --- /dev/null +++ b/.github/dotnet.json @@ -0,0 +1,4 @@ +[ + "6.x", + "8.x" +] diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 564672fd1..94db3ffe6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ on: - Release - Debug push: - branches: [ main, dev, 'dev/*', 'feature/*', 'rel/*' ] + branches: [ main, 'feature/*', 'rel/*' ] paths-ignore: - changelog.md - readme.md @@ -66,15 +66,14 @@ jobs: fetch-depth: 0 - name: โš™ dotnet - uses: ./.github/actions/dotnet + uses: devlooped/actions-dotnet-env@v1 - name: ๐Ÿ™ build run: dotnet build -m:1 -bl:build.binlog - name: ๐Ÿงช test - run: | - dotnet tool update -g dotnet-retest - dotnet retest -- --no-build + shell: pwsh + run: dnx --yes retest -- --no-build - name: ๐Ÿ› logs uses: actions/upload-artifact@v4 @@ -101,12 +100,7 @@ jobs: fetch-depth: 0 - name: โš™ dotnet - uses: actions/setup-dotnet@v4 - with: - dotnet-version: | - 6.x - 8.x - 9.x + uses: devlooped/actions-dotnet-env@v1 - name: โœ“ ensure format run: | diff --git a/.github/workflows/dotnet-env.yml b/.github/workflows/dotnet-env.yml new file mode 100644 index 000000000..1206d10f4 --- /dev/null +++ b/.github/workflows/dotnet-env.yml @@ -0,0 +1,52 @@ +name: dotnet-env +on: + workflow_dispatch: + push: + branches: + - main + paths: + - '**/*.*proj' + +jobs: + which-dotnet: + runs-on: ubuntu-latest + env: + PR_TOKEN: ${{ secrets.DEVLOOPED_TOKEN || secrets.GH_TOKEN }} + permissions: + contents: write + pull-requests: write + + steps: + - name: ๐Ÿค– defaults + uses: devlooped/actions-bot@v1 + with: + name: ${{ secrets.BOT_NAME }} + email: ${{ secrets.BOT_EMAIL }} + gh_token: ${{ env.PR_TOKEN }} + github_token: ${{ secrets.GITHUB_TOKEN }} + + - name: ๐Ÿค˜ checkout + uses: actions/checkout@v4 + with: + token: ${{ env.PR_TOKEN || github.token }} + + - name: ๐ŸคŒ dotnet + uses: devlooped/actions-which-dotnet@v1 + + - name: โœ pull request + if: env.PR_TOKEN != '' + uses: peter-evans/create-pull-request@v7 + with: + base: main + branch: which-dotnet + delete-branch: true + labels: dependencies + title: "โš™ Update dotnet versions" + body: "Update dotnet versions" + commit-message: "Update dotnet versions" + token: ${{ env.PR_TOKEN }} + + - name: โš ๏ธ skip pull request + if: env.PR_TOKEN == '' + shell: bash + run: echo "::warning::Skipping PR creation because neither DEVLOOPED_TOKEN nor GH_TOKEN is configured. GITHUB_TOKEN cannot create pull requests in this repository." diff --git a/.github/workflows/includes.yml b/.github/workflows/includes.yml index d787ccb95..8fd66901e 100644 --- a/.github/workflows/includes.yml +++ b/.github/workflows/includes.yml @@ -5,12 +5,15 @@ on: branches: - 'main' paths: - - '**.md' + - '**.md' - '!changelog.md' + - 'osmfeula.txt' jobs: includes: runs-on: ubuntu-latest + env: + PR_TOKEN: ${{ secrets.DEVLOOPED_TOKEN || secrets.GH_TOKEN }} permissions: contents: write pull-requests: write @@ -20,28 +23,53 @@ jobs: with: name: ${{ secrets.BOT_NAME }} email: ${{ secrets.BOT_EMAIL }} - gh_token: ${{ secrets.GH_TOKEN }} + gh_token: ${{ env.PR_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }} - name: ๐Ÿค˜ checkout uses: actions/checkout@v4 with: - token: ${{ env.GH_TOKEN }} + token: ${{ env.PR_TOKEN || github.token }} - name: +Mแ includes uses: devlooped/actions-includes@v1 + - name: ๐Ÿ“ OSMF EULA + shell: pwsh + run: | + $file = "osmfeula.txt" + $props = "src/Directory.Build.props" + if (-not (test-path $file) -or -not (test-path $props)) { + exit 0 + } + + $product = dotnet msbuild $props -getproperty:Product + if (-not $product) { + write-error 'To use OSMF EULA, ensure the $(Product) property is set in Directory.props' + exit 1 + } + + ((get-content -raw $file) -replace '\$product\$',$product).trim() | set-content $file + - name: โœ pull request - uses: peter-evans/create-pull-request@v6 + if: env.PR_TOKEN != '' + uses: peter-evans/create-pull-request@v8 with: - add-paths: '**.md' + add-paths: | + **.md + *.txt base: main branch: markdown-includes delete-branch: true - labels: docs + labels: dependencies author: ${{ env.BOT_AUTHOR }} committer: ${{ env.BOT_AUTHOR }} commit-message: +Mแ includes title: +Mแ includes body: +Mแ includes - token: ${{ env.GH_TOKEN }} + token: ${{ env.PR_TOKEN }} + + - name: โš ๏ธ skip pull request + if: env.PR_TOKEN == '' + shell: bash + run: echo "::warning::Skipping PR creation because neither DEVLOOPED_TOKEN nor GH_TOKEN is configured. GITHUB_TOKEN cannot create pull requests in this repository." diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 4ffa001cc..035d81103 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -28,15 +28,14 @@ jobs: fetch-depth: 0 - name: โš™ dotnet - uses: ./.github/actions/dotnet + uses: devlooped/actions-dotnet-env@v1 - name: ๐Ÿ™ build run: dotnet build -m:1 -bl:build.binlog - name: ๐Ÿงช test - run: | - dotnet tool update -g dotnet-retest - dotnet retest -- --no-build + shell: pwsh + run: dnx --yes retest -- --no-build - name: ๐Ÿ› logs uses: actions/upload-artifact@v4 diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml index 56ff299a2..99eec7687 100644 --- a/.github/workflows/triage.yml +++ b/.github/workflows/triage.yml @@ -49,7 +49,11 @@ jobs: # if we don't have at least 100 requests left, wait until reset if ($rate.remaining -lt 100) { $wait = ($rate.reset - (Get-Date (Get-Date).ToUniversalTime() -UFormat %s)) - echo "Rate limit remaining is $($rate.remaining), waiting for $($wait / 1000) seconds to reset" + if ($wait -gt 300) { + echo "Rate limit remaining is $($rate.remaining), reset in $wait seconds (more than 5'). Aborting." + exit 1 + } + echo "Rate limit remaining is $($rate.remaining), waiting $wait seconds to reset" sleep $wait $rate = gh api rate_limit | convertfrom-json | select -expandproperty rate echo "Rate limit has reset to $($rate.remaining) requests" diff --git a/.gitignore b/.gitignore index 2ac54a728..25e70dd97 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,24 @@ bin obj +out artifacts pack +agent-tools +terminals TestResults results BenchmarkDotNet.Artifacts +mcps /app +/temp .vs .vscode .genaiscript .idea local.settings.json +.env +.next +*.local *.suo *.sdf @@ -24,6 +32,7 @@ local.settings.json *.binlog *.zip __azurite*.* +AzuriteConfig __*__ .nuget diff --git a/.netconfig b/.netconfig index 588e82b60..2ac02a087 100644 --- a/.netconfig +++ b/.netconfig @@ -5,8 +5,8 @@ skip [file ".github/workflows/includes.yml"] url = https://github.com/devlooped/oss/blob/main/.github/workflows/includes.yml - sha = 85829f2510f335f4a411867f3dbaaa116c3ab3de - etag = 086f6b6316cc6ea7089c0dcc6980be519e6ed6e6201e65042ef41b82634ec0ee + sha = 7c1c6e615b5785e0ac9db33cb17343d6c1de16ff + etag = 5e6a10be141ee629201bfad01eae09b5c36a67f541ec7ab411ae400b5d73de1d weak [file ".github/workflows/dotnet-file.yml"] url = https://github.com/devlooped/oss/blob/main/.github/workflows/dotnet-file.yml @@ -23,8 +23,8 @@ skip [file ".editorconfig"] url = https://github.com/devlooped/oss/blob/main/.editorconfig - sha = c779d3d4e468358106dea03e93ba2cd35bb01ecb - etag = 7298c6450967975a8782b5c74f3071e1910fc59686e48f9c9d5cd7c68213cf59 + sha = a62c45934ac2952f2f5d54d8aea4a7ebc1babaff + etag = b5e919b472a52d4b522f86494f0f2c0ba74a6d9601454e20e4cbaf744317ff62 weak [file ".gitattributes"] url = https://github.com/devlooped/oss/blob/main/.gitattributes @@ -34,8 +34,8 @@ weak [file ".github/dependabot.yml"] url = https://github.com/devlooped/oss/blob/main/.github/dependabot.yml - sha = 917ff5486e25bec90038e7ab6d146fd82c61f846 - etag = 50bf50df5a6eeb1705baea50f4c6e06d167a89cb5a590887ff939bd4120bd442 + sha = 387f0616b2c56adc4f33f2858da818f7e0d92ef3 + etag = a1aaba1440e5ee2a7b7f93fc0fb004d4e1d4d9780350c753f7c429e37241345a weak [file ".github/release.yml"] url = https://github.com/devlooped/oss/blob/main/.github/release.yml @@ -45,9 +45,9 @@ weak [file ".github/workflows/build.yml"] url = https://github.com/devlooped/oss/blob/main/.github/workflows/build.yml - sha = 08c70776943839f73dbea2e65355108747468508 + sha = 5da103cfbc1c4f9b5f59cfa698d2afbd744a7525 - etag = fb2e91cdc9fb7a4d3e8f698e525816c5d8febb35b005c278eecca8056e78f809 + etag = 851af098748f7cfa5bc3cfd4cc404a6de930532b59ceb2b3b535282c41226f3a weak [file ".github/workflows/changelog.config"] url = https://github.com/devlooped/oss/blob/main/.github/workflows/changelog.config @@ -66,14 +66,14 @@ weak [file ".github/workflows/publish.yml"] url = https://github.com/devlooped/oss/blob/main/.github/workflows/publish.yml - sha = 08c70776943839f73dbea2e65355108747468508 + sha = 7f5f9ee9f362f7e8f951d618f8f799033550e687 - etag = 722a2c7cb3a42bc24ca7fb48d2e9a336641ed0599418239e24efbafccf64bd50 + etag = c60411d1aa4e98e7f69e2d34cbccb8eb7e387ec11f6f8e78ee8d8b92122d7025 weak [file ".gitignore"] url = https://github.com/devlooped/oss/blob/main/.gitignore - sha = e0be248fff1d39133345283b8227372b36574b75 - etag = c449ec6f76803e1891357ca2b8b4fcb5b2e5deeff8311622fd92ca9fbf1e6575 + sha = ff61659751374b95c7a8a0477c908a8119f756f0 + etag = e5865f083db45081a7b4eaa518018971b34e6ef93f917ac510dea96d27f792b3 weak [file "Directory.Build.rsp"] url = https://github.com/devlooped/oss/blob/main/Directory.Build.rsp @@ -82,8 +82,8 @@ weak [file "_config.yml"] url = https://github.com/devlooped/oss/blob/main/_config.yml - sha = fa83a5161ba52bc5d510ce0ba75ee0b1f8d4bc63 - etag = 9139148f845adf503fd3c3c140eb64421fc476a1f9c027fc50825c0efb05f557 + sha = 68b409c486842062e0de0e5b11e6fdb7cd12d6e2 + etag = d608aa0ddaedc2d8a87260f50756e8d8314964ad4671b76bd085bcb458757010 weak [file "assets/css/style.scss"] url = https://github.com/devlooped/oss/blob/main/assets/css/style.scss @@ -95,14 +95,14 @@ skip [file "src/Directory.Build.props"] url = https://github.com/devlooped/oss/blob/main/src/Directory.Build.props - sha = 2fff747a9673b499c99f2da183cdd5263fdc9333 - etag = 0fccddf04f282fe98122ab2610dc2972c205a521254559bf013655c6271b0017 + sha = 6e2438919e108aeb75106dc0737c45f5e55d5f42 + etag = f1d6384abf18d8d891ce5e835a10c73fe029c42151374be96d7e4af43d189c65 weak [file "src/Directory.Build.targets"] url = https://github.com/devlooped/oss/blob/main/src/Directory.Build.targets - sha = a8b208093599263b7f2d1fe3854634c588ea5199 + sha = dcd5f0a9a00a5b0c23d41a71dcc66ea41dc5f75d - etag = 19087699f05396205e6b050d999a43b175bd242f6e8fac86f6df936310178b03 + etag = 2cca66d8a1adbae24dc2efee53a55fa09949242eb35b86c5fd66425da18c6107 weak [file "src/kzu.snk"] url = https://github.com/devlooped/oss/blob/main/src/kzu.snk @@ -112,8 +112,8 @@ skip [file ".github/workflows/triage.yml"] url = https://github.com/devlooped/oss/blob/main/.github/workflows/triage.yml - sha = 33000c0c4ab4eb4e0e142fa54515b811a189d55c - etag = 013a47739e348f06891f37c45164478cca149854e6cd5c5158e6f073f852b61a + sha = 61a602fc61eedbdae235f01e93657a6219ac2427 + etag = 152cd3a559c08da14d1da12a5262ba1d2e0ed6bed6d2eabf5bd209b0c35d8a75 weak [file ".github/workflows/dotnet-file-core.yml"] url = https://github.com/devlooped/oss/blob/main/.github/workflows/dotnet-file-core.yml @@ -126,3 +126,19 @@ [file "osmfeula.txt"] url = https://github.com/devlooped/.github/blob/main/osmfeula.txt skip +[file "readme.tmp.md"] + url = https://github.com/devlooped/oss/blob/main/readme.tmp.md + skip +[file "oss.cs"] + url = https://github.com/devlooped/oss/blob/main/oss.cs + skip +[file ".github/copilot-instructions.md"] + url = https://github.com/devlooped/oss/blob/main/.github/copilot-instructions.md + sha = e616d89d9537c4b8ccf1c20dd277ab82104167c4 + etag = 6ee650d118a57494d3545d54718ccaa5257b09d54504e9c21514fe596edd9678 + weak +[file ".github/workflows/dotnet-env.yml"] + url = https://github.com/devlooped/oss/blob/main/.github/workflows/dotnet-env.yml + sha = 7c1c6e615b5785e0ac9db33cb17343d6c1de16ff + etag = 68c1e28f475ff9d05f985bf53a51fce6e64b24a8b75bd8e47119ff57309281f1 + weak diff --git a/_config.yml b/_config.yml index a61f7e0bf..04e609390 100644 --- a/_config.yml +++ b/_config.yml @@ -1,3 +1,3 @@ theme: jekyll-theme-slate -exclude: [ 'src/', '*.sln', 'Gemfile*', '*.rsp' ] \ No newline at end of file +exclude: [ 'src/', '*.sln', '*.slnx', 'Gemfile*', '*.rsp' ] diff --git a/changelog.md b/changelog.md index 252a101b7..b9c73981b 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +:bug: Fixed bugs: + +- FileLoadException for 'System.Threading.Tasks.Extensions, Version=4.2.0.1' (or similar) at `Moq.Async.AwaitableFactory..cctor()` on .NET Framework 4.6.2+ no longer occurs when updating Moq and other dependencies bring in newer versions of the package. Common non-ValueTask usage succeeds without binding redirects. The initialization now only eagerly registers Task providers and uses runtime FullName checks + deferred creation for ValueTask support. [#1648](https://github.com/devlooped/moq/issues/1648) + ## [v4.20.72](https://github.com/devlooped/moq/tree/v4.20.72) (2024-09-07) [Full Changelog](https://github.com/devlooped/moq/compare/v4.20.71...v4.20.72) diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index cb3caffeb..37391b215 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -1,10 +1,10 @@ GEM remote: https://rubygems.org/ specs: - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) + addressable (2.9.0) + public_suffix (>= 2.0.2, < 8.0) colorator (1.1.0) - concurrent-ruby (1.1.9) + concurrent-ruby (1.3.7) em-websocket (0.5.2) eventmachine (>= 0.12.9) http_parser.rb (~> 0.6.0) @@ -46,11 +46,11 @@ GEM mercenary (0.4.0) pathutil (0.16.2) forwardable-extended (~> 2.6) - public_suffix (4.0.6) + public_suffix (7.0.5) rb-fsevent (0.11.0) rb-inotify (0.10.1) ffi (~> 1.0) - rexml (3.2.5) + rexml (3.3.9) rouge (3.26.0) safe_yaml (1.0.5) sassc (2.4.0) @@ -60,6 +60,7 @@ GEM unicode-display_width (1.7.0) PLATFORMS + x86_64-linux x86_64-linux-musl DEPENDENCIES diff --git a/readme.md b/readme.md index af6077b6a..a9c27e7a2 100644 --- a/readme.md +++ b/readme.md @@ -109,44 +109,37 @@ We appreciate deeply any feedback that you may have! Feel free to participate in # Sponsors -[![Clarius Org](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/clarius.png "Clarius Org")](https://github.com/clarius) -[![MFB Technologies, Inc.](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/MFB-Technologies-Inc.png "MFB Technologies, Inc.")](https://github.com/MFB-Technologies-Inc) -[![Torutek](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/torutek-gh.png "Torutek")](https://github.com/torutek-gh) -[![DRIVE.NET, Inc.](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/drivenet.png "DRIVE.NET, Inc.")](https://github.com/drivenet) -[![Keith Pickford](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Keflon.png "Keith Pickford")](https://github.com/Keflon) -[![Thomas Bolon](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/tbolon.png "Thomas Bolon")](https://github.com/tbolon) -[![Kori Francis](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/kfrancis.png "Kori Francis")](https://github.com/kfrancis) -[![Toni Wenzel](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/twenzel.png "Toni Wenzel")](https://github.com/twenzel) -[![Uno Platform](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/unoplatform.png "Uno Platform")](https://github.com/unoplatform) -[![Dan Siegel](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/dansiegel.png "Dan Siegel")](https://github.com/dansiegel) -[![Reuben Swartz](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/rbnswartz.png "Reuben Swartz")](https://github.com/rbnswartz) -[![Jacob Foshee](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/jfoshee.png "Jacob Foshee")](https://github.com/jfoshee) -[![](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Mrxx99.png "")](https://github.com/Mrxx99) -[![Eric Johnson](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/eajhnsn1.png "Eric Johnson")](https://github.com/eajhnsn1) -[![David JENNI](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/davidjenni.png "David JENNI")](https://github.com/davidjenni) -[![Jonathan ](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Jonathan-Hickey.png "Jonathan ")](https://github.com/Jonathan-Hickey) -[![Charley Wu](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/akunzai.png "Charley Wu")](https://github.com/akunzai) -[![Ken Bonny](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/KenBonny.png "Ken Bonny")](https://github.com/KenBonny) -[![Simon Cropp](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/SimonCropp.png "Simon Cropp")](https://github.com/SimonCropp) -[![agileworks-eu](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/agileworks-eu.png "agileworks-eu")](https://github.com/agileworks-eu) -[![sorahex](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/sorahex.png "sorahex")](https://github.com/sorahex) -[![Zheyu Shen](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/arsdragonfly.png "Zheyu Shen")](https://github.com/arsdragonfly) -[![Vezel](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/vezel-dev.png "Vezel")](https://github.com/vezel-dev) -[![ChilliCream](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/ChilliCream.png "ChilliCream")](https://github.com/ChilliCream) -[![4OTC](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/4OTC.png "4OTC")](https://github.com/4OTC) -[![Vincent Limo](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/v-limo.png "Vincent Limo")](https://github.com/v-limo) -[![Jordan S. Jones](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/jordansjones.png "Jordan S. Jones")](https://github.com/jordansjones) -[![domischell](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/DominicSchell.png "domischell")](https://github.com/DominicSchell) -[![Mauricio Scheffer](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/mausch.png "Mauricio Scheffer")](https://github.com/mausch) -[![Justin Wendlandt](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/jwendl.png "Justin Wendlandt")](https://github.com/jwendl) -[![Adrian Alonso](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/adalon.png "Adrian Alonso")](https://github.com/adalon) -[![Michael Hagedorn](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/Eule02.png "Michael Hagedorn")](https://github.com/Eule02) +[![Clarius Org](https://avatars.githubusercontent.com/u/71888636?v=4&s=39 "Clarius Org")](https://github.com/clarius) +[![MFB Technologies, Inc.](https://avatars.githubusercontent.com/u/87181630?v=4&s=39 "MFB Technologies, Inc.")](https://github.com/MFB-Technologies-Inc) +[![SandRock](https://avatars.githubusercontent.com/u/321868?u=99e50a714276c43ae820632f1da88cb71632ec97&v=4&s=39 "SandRock")](https://github.com/sandrock) +[![DRIVE.NET, Inc.](https://avatars.githubusercontent.com/u/15047123?v=4&s=39 "DRIVE.NET, Inc.")](https://github.com/drivenet) +[![Keith Pickford](https://avatars.githubusercontent.com/u/16598898?u=64416b80caf7092a885f60bb31612270bffc9598&v=4&s=39 "Keith Pickford")](https://github.com/Keflon) +[![Thomas Bolon](https://avatars.githubusercontent.com/u/127185?u=7f50babfc888675e37feb80851a4e9708f573386&v=4&s=39 "Thomas Bolon")](https://github.com/tbolon) +[![Kori Francis](https://avatars.githubusercontent.com/u/67574?u=3991fb983e1c399edf39aebc00a9f9cd425703bd&v=4&s=39 "Kori Francis")](https://github.com/kfrancis) +[![Reuben Swartz](https://avatars.githubusercontent.com/u/724704?u=2076fe336f9f6ad678009f1595cbea434b0c5a41&v=4&s=39 "Reuben Swartz")](https://github.com/rbnswartz) +[![Jacob Foshee](https://avatars.githubusercontent.com/u/480334?v=4&s=39 "Jacob Foshee")](https://github.com/jfoshee) +[![](https://avatars.githubusercontent.com/u/33566379?u=bf62e2b46435a267fa246a64537870fd2449410f&v=4&s=39 "")](https://github.com/Mrxx99) +[![Eric Johnson](https://avatars.githubusercontent.com/u/26369281?u=41b560c2bc493149b32d384b960e0948c78767ab&v=4&s=39 "Eric Johnson")](https://github.com/eajhnsn1) +[![Jonathan ](https://avatars.githubusercontent.com/u/5510103?u=98dcfbef3f32de629d30f1f418a095bf09e14891&v=4&s=39 "Jonathan ")](https://github.com/Jonathan-Hickey) +[![Ken Bonny](https://avatars.githubusercontent.com/u/6417376?u=569af445b6f387917029ffb5129e9cf9f6f68421&v=4&s=39 "Ken Bonny")](https://github.com/KenBonny) +[![Simon Cropp](https://avatars.githubusercontent.com/u/122666?v=4&s=39 "Simon Cropp")](https://github.com/SimonCropp) +[![agileworks-eu](https://avatars.githubusercontent.com/u/5989304?v=4&s=39 "agileworks-eu")](https://github.com/agileworks-eu) +[![Zheyu Shen](https://avatars.githubusercontent.com/u/4067473?v=4&s=39 "Zheyu Shen")](https://github.com/arsdragonfly) +[![Vezel](https://avatars.githubusercontent.com/u/87844133?v=4&s=39 "Vezel")](https://github.com/vezel-dev) +[![ChilliCream](https://avatars.githubusercontent.com/u/16239022?v=4&s=39 "ChilliCream")](https://github.com/ChilliCream) +[![4OTC](https://avatars.githubusercontent.com/u/68428092?v=4&s=39 "4OTC")](https://github.com/4OTC) +[![domischell](https://avatars.githubusercontent.com/u/66068846?u=0a5c5e2e7d90f15ea657bc660f175605935c5bea&v=4&s=39 "domischell")](https://github.com/DominicSchell) +[![Adrian Alonso](https://avatars.githubusercontent.com/u/2027083?u=129cf516d99f5cb2fd0f4a0787a069f3446b7522&v=4&s=39 "Adrian Alonso")](https://github.com/adalon) +[![torutek](https://avatars.githubusercontent.com/u/33917059?v=4&s=39 "torutek")](https://github.com/torutek) +[![Ryan McCaffery](https://avatars.githubusercontent.com/u/16667079?u=c0daa64bb5c1b572130e05ae2b6f609ecc912d4d&v=4&s=39 "Ryan McCaffery")](https://github.com/mccaffers) +[![Seika Logiciel](https://avatars.githubusercontent.com/u/2564602?v=4&s=39 "Seika Logiciel")](https://github.com/SeikaLogiciel) +[![Andrew Grant](https://avatars.githubusercontent.com/devlooped-user?s=39 "Andrew Grant")](https://github.com/wizardness) +[![eska-gmbh](https://avatars.githubusercontent.com/devlooped-team?s=39 "eska-gmbh")](https://github.com/eska-gmbh) +[![Geodata AS](https://avatars.githubusercontent.com/u/5946299?v=4&s=39 "Geodata AS")](https://github.com/geodata-no) - -[![Sponsor this project](https://raw.githubusercontent.com/devlooped/sponsors/main/sponsor.png "Sponsor this project")](https://github.com/sponsors/devlooped) -  +[![Sponsor this project](https://avatars.githubusercontent.com/devlooped-sponsor?s=118 "Sponsor this project")](https://github.com/sponsors/devlooped) [Learn more about GitHub Sponsors](https://github.com/sponsors) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index b0b9d94d9..93a0b1e43 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,4 +1,4 @@ - + @@ -20,6 +20,7 @@ Daniel Cazzulino + Devlooped Copyright (C) Daniel Cazzulino and Contributors. All rights reserved. false MIT @@ -42,6 +43,13 @@ true + + false + + true + + + false @@ -126,11 +134,22 @@ <_VersionLabel>$(_VersionLabel.Replace('/merge', '')) <_VersionLabel>$(_VersionLabel.Replace('/', '-')) + + <_VersionLabel>$(_VersionLabel.Replace('_', '-')) $(_VersionLabel) $(_VersionLabel) + + + true + 42.42.0 + $(VersionSuffix).$(GITHUB_RUN_NUMBER) @@ -150,6 +169,10 @@ + + + + diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index 6232750f3..bd2b7ca7d 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -32,6 +32,22 @@ true + + + <_PackageId>$(PackageId) + + $(MSBuildSDKsPath)\..\NuGet.Build.Tasks.Pack.targets + + + + + + + $(_PackageId) + + false + + @@ -165,22 +181,35 @@ @(_GitSourceRoot) + + $([System.IO.Path]::GetFileNameWithoutExtension($(PrivateRepositoryUrl))) + $(ProductFromUrl) - $(RepositoryUrl) + $(RepositoryUrl.Replace('.git', '')) $(Description) - $(RepositoryUrl)/blob/main/changelog.md + $(RepositoryUrl.Replace('.git', ''))/blob/main/changelog.md + + + + + OSMFEULA.txt + true + + + + diff --git a/src/Moq.Tests.FSharpTypes/Moq.Tests.FSharpTypes.fsproj b/src/Moq.Tests.FSharpTypes/Moq.Tests.FSharpTypes.fsproj index b938824de..49eec4907 100644 --- a/src/Moq.Tests.FSharpTypes/Moq.Tests.FSharpTypes.fsproj +++ b/src/Moq.Tests.FSharpTypes/Moq.Tests.FSharpTypes.fsproj @@ -8,13 +8,13 @@ - + - + - + diff --git a/src/Moq.Tests.Issue1648Harness/Issue1648Harness.csproj b/src/Moq.Tests.Issue1648Harness/Issue1648Harness.csproj new file mode 100644 index 000000000..f7f1840ca --- /dev/null +++ b/src/Moq.Tests.Issue1648Harness/Issue1648Harness.csproj @@ -0,0 +1,31 @@ + + + + Exe + net472 + 10.0 + + $(NoWarn);NU1605 + true + + + + + + + System.Threading.Tasks.Extensions.dll + PreserveNewest + false + + + + + + + + + + diff --git a/src/Moq.Tests.Issue1648Harness/Program.cs b/src/Moq.Tests.Issue1648Harness/Program.cs new file mode 100644 index 000000000..4191af728 --- /dev/null +++ b/src/Moq.Tests.Issue1648Harness/Program.cs @@ -0,0 +1,90 @@ +// Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD, and Contributors. +// Committed harness exe for #1648 verification (net472). +// Deliberately never mentions ValueTask. Preloads older Tasks.Extensions to simulate +// version conflict, hooks resolve to count/throw real FileLoad on hit, cold-loads +// the passed Moq dll via LoadFrom, reflects to call shipped TryGet(Task) and TryGet(Task). +// Used by thin test in IssueReportsFixture; output markers drive assertions. +// Build produces the exe + 4.5.4 dll next to it. + +using System; +using System.IO; +using System.Reflection; + +class Program +{ + static int Main(string[] args) + { + if (args.Length < 1) { Console.WriteLine("usage: Issue1648Harness [ext.dll]"); return 2; } + string moqPath = args[0]; + string extPath = args.Length > 1 ? args[1] : null; + int resolveCount = 0; + bool hit = false; + + // Force preload of the specific old version next to this exe (the 4.5.4 copy). + // This guarantees PRELOADED_VERSION=4.2.0.1 and genuine conflict sim when Moq (4.6.3 dep) is LoadFrom'ed. + Assembly pre = null; + string localExt = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "System.Threading.Tasks.Extensions.dll"); + if (File.Exists(localExt)) + { + try { pre = Assembly.LoadFrom(localExt); } catch { /* will fall back */ } + } + if (pre == null && !string.IsNullOrEmpty(extPath) && File.Exists(extPath)) + { + try { pre = Assembly.LoadFrom(extPath); } catch { } + } + // Last resort: scan already loaded + if (pre == null) + { + foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) + { + if ((asm.GetName().Name ?? "").IndexOf("Tasks.Extensions", StringComparison.OrdinalIgnoreCase) >= 0) + { + pre = asm; break; + } + } + } + + AppDomain.CurrentDomain.AssemblyResolve += (s, e) => + { + if ((e.Name ?? "").IndexOf("Tasks.Extensions", StringComparison.OrdinalIgnoreCase) >= 0) + { + resolveCount++; + hit = true; + // Throw the exact FileLoad customers saw when eager cctor hit a mismatched version. + throw new FileLoadException("Could not load file or assembly 'System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)"); + } + return null; + }; + + object f1 = null; + Exception ex1 = null; + + // Load the Moq under test (cold) -- this is what runs the AwaitableFactory cctor in the loaded assembly. + try + { + var moq = Assembly.LoadFrom(moqPath); + var t = moq.GetType("Moq.Async.AwaitableFactory", true); + var m = t.GetMethod("TryGet", BindingFlags.Public | BindingFlags.Static); + + // Only Task -- the critical common non-VT path that must not trigger the bad resolve/FileLoad on cctor. + try + { + Type tt = Type.GetType("System.Threading.Tasks.Task"); + f1 = m.Invoke(null, new object[] { tt }); + } + catch (Exception e) { ex1 = e; } + } + catch (Exception e) { if (ex1 == null) ex1 = e; } + + string preVer = pre != null ? pre.GetName().Version.ToString() : "none"; + Console.WriteLine("PRELOADED_VERSION=" + preVer); + Console.WriteLine("COLD_RESOLVE_COUNT=" + resolveCount); + Console.WriteLine("HIT_FILELOAD_SIM=" + hit); + // OK if no resolve was hit during the cold LoadFrom + cctor + TryGet(Task). Inner f1/ex is secondary for this proof. + bool coreOk = (resolveCount == 0 && !hit); + Console.WriteLine("COLD_LOAD_OK=" + coreOk); + if (ex1 != null) Console.WriteLine("EX=" + ex1.GetType().Name); + // Return 0 only if the resolve hook was never fired for Tasks.Extensions (the fix). + return (resolveCount == 0 && !hit) ? 0 : 1; + } +} diff --git a/src/Moq.Tests.VisualBasic/Moq.Tests.VisualBasic.vbproj b/src/Moq.Tests.VisualBasic/Moq.Tests.VisualBasic.vbproj index 7a30fc8ab..0367039dc 100644 --- a/src/Moq.Tests.VisualBasic/Moq.Tests.VisualBasic.vbproj +++ b/src/Moq.Tests.VisualBasic/Moq.Tests.VisualBasic.vbproj @@ -8,11 +8,11 @@ - + - + diff --git a/src/Moq.Tests/EventHandlersFixture.cs b/src/Moq.Tests/EventHandlersFixture.cs index 0338573bf..e08965d40 100644 --- a/src/Moq.Tests/EventHandlersFixture.cs +++ b/src/Moq.Tests/EventHandlersFixture.cs @@ -179,11 +179,29 @@ public async Task Can_raise_parameterized_async_event_using_RaiseAsync() Assert.Equal(42, received); } - public class HasAsyncEvent + [Fact] + public async Task RaiseAsync_WithVoidEventDelegate_ThrowsArgumentOutOfRangeException() { -#pragma warning disable CS0067 // Event never used - public virtual event Func Event; - public virtual event Func ParameterizedEvent; + //Arrange + var mock = new Mock(); + + await Assert.ThrowsAnyAsync(async () => await mock.RaiseAsync(e => e.CustomEvent += null, "foo", 5)); } + } } + +public delegate void CustomEvent(string message, int value); + +public interface IWithEvent +{ + event CustomEvent CustomEvent; + +} + +public class HasAsyncEvent +{ +#pragma warning disable CS0067 // Event never used + public virtual event Func Event; + public virtual event Func ParameterizedEvent; +} diff --git a/src/Moq.Tests/Moq.Tests.csproj b/src/Moq.Tests/Moq.Tests.csproj index 5649a70cd..101459f25 100644 --- a/src/Moq.Tests/Moq.Tests.csproj +++ b/src/Moq.Tests/Moq.Tests.csproj @@ -21,18 +21,20 @@ + + - + - + - + - + @@ -42,8 +44,17 @@ + + + + + + + + + - + diff --git a/src/Moq.Tests/Regressions/IssueReportsFixture.cs b/src/Moq.Tests/Regressions/IssueReportsFixture.cs index 2d81c65a6..cdc009ce1 100644 --- a/src/Moq.Tests/Regressions/IssueReportsFixture.cs +++ b/src/Moq.Tests/Regressions/IssueReportsFixture.cs @@ -2,6 +2,7 @@ // All rights reserved. Licensed under the BSD 3-Clause License; see License.txt. using System; +using System.CodeDom.Compiler; using System.Collections; using System.Collections.Generic; using System.ComponentModel; @@ -13,15 +14,12 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; - using Castle.DynamicProxy; - +using Microsoft.CSharp; using Microsoft.Extensions.Logging; - using Moq; using Moq.Properties; using Moq.Protected; - using Xunit; #region #181 @@ -4590,6 +4588,145 @@ public interface IParams #endregion + #region #1648 + + public class Issue1648 + { + [Fact] + public void AwaitableFactory_TryGet_for_Task_succeeds_without_premature_ValueTask_assembly_load() + { + // Thin test: drives the *shipped* Moq code (real TryGet) via the committed separate harness exe + // (built with 4.5.4 dep) under real conflict. No CodeDom, no scratch I/O, no early returns that skip. + // The harness exe (src/Moq.Tests.Issue1648Harness) is the cold-load driver. + + string moq462 = typeof(Moq.Async.AwaitableFactory).Assembly.Location; + Assert.True(File.Exists(moq462), "Moq assembly not found at " + moq462); + + // Locate the pre-built harness exe and its copied 4.5.4 ext (from its bin layout). + // Strategy: from this test's dir, walk to find the harness output. + string harnessDir = FindHarnessOutputDir(); + string harnessExe = Path.Combine(harnessDir, "Issue1648Harness.exe"); + Assert.True(File.Exists(harnessExe), "Harness exe not found (build Moq.Tests.Issue1648Harness first): " + harnessExe); + + // Must use the ext from the harness output dir (the one we explicitly copied as the older conflicting version). + // No fallback to 'any' dll. + string ext454 = Directory.GetFiles(harnessDir, "System.Threading.Tasks.Extensions.dll", SearchOption.AllDirectories).FirstOrDefault(); + Assert.False(string.IsNullOrEmpty(ext454) || !File.Exists(ext454), "REQUIRED: Tasks.Extensions.dll from harness output dir (the explicitly copied older one). Build harness project."); + // Verify older for mismatch. + var extAsmVer = System.Reflection.AssemblyName.GetAssemblyName(ext454).Version; + Assert.True(extAsmVer < new Version(4, 6, 0), "Must use pre-4.6 version for real mismatch (got " + extAsmVer + ")"); + + // Launch the harness (it preloads the ext, sets resolve that throws real FileLoad on hit, LoadFrom Moq, TryGet Task only). + var psi = new System.Diagnostics.ProcessStartInfo(harnessExe, $"\"{moq462}\" \"{ext454}\""); + psi.UseShellExecute = false; + psi.RedirectStandardOutput = true; + psi.RedirectStandardError = true; + psi.CreateNoWindow = true; + var p = System.Diagnostics.Process.Start(psi); + string stdout = p.StandardOutput.ReadToEnd(); + string stderr = p.StandardError.ReadToEnd(); + p.WaitForExit(); + + Assert.Contains("COLD_RESOLVE_COUNT=0", stdout); + Assert.DoesNotContain("HIT_FILELOAD_SIM=True", stdout); + Assert.DoesNotContain("FileLoadException", stdout, StringComparison.OrdinalIgnoreCase); + Assert.Equal(0, p.ExitCode); + // Genuine older conflicting preload (e.g. 4.2.x from 4.5.x package). + Assert.Contains("PRELOADED_VERSION=4.2", stdout); + + // Drive the real shipped API directly (after the child proved the cold path). + var f = Moq.Async.AwaitableFactory.TryGet(typeof(Task)); + Assert.NotNull(f); + var fT = Moq.Async.AwaitableFactory.TryGet(typeof(Task)); + Assert.NotNull(fT); + } + + [Fact] + public void Legacy_cctor_IL_guard_only_Task_tokens() + { + var asm = typeof(Moq.Async.AwaitableFactory).Assembly; + var legacy = asm.GetType("Moq.Async.LegacyAwaitableFactory"); + if (legacy == null) return; // modern build + // Force cctor by accessing the providers set in static init + var p = legacy.GetField("Providers", BindingFlags.NonPublic | BindingFlags.Static); + var dict = p.GetValue(null) as System.Collections.IDictionary; + Assert.NotNull(dict); + foreach (Type k in dict.Keys) + { + Assert.True(k == typeof(Task) || k == typeof(Task<>), "Legacy cctor must only register Task providers (no ValueTask in init)"); + } + } + + static string FindHarnessOutputDir() + { + // 1. Standard pre-built locations from the committed harness project. + var dir = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory); + for (int i = 0; i < 12 && dir != null; i++) + { + var cand = Path.Combine(dir.FullName, "Moq.Tests.Issue1648Harness", "bin", "Release", "net472"); + if (Directory.Exists(cand) && File.Exists(Path.Combine(cand, "Issue1648Harness.exe"))) return cand; + var candDbg = Path.Combine(dir.FullName, "Moq.Tests.Issue1648Harness", "bin", "Debug", "net472"); + if (Directory.Exists(candDbg) && File.Exists(Path.Combine(candDbg, "Issue1648Harness.exe"))) return candDbg; + dir = dir.Parent; + } + var baseSrc = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "..", "..", "src", "Moq.Tests.Issue1648Harness", "bin", "Release", "net472")); + if (File.Exists(Path.Combine(baseSrc, "Issue1648Harness.exe"))) return baseSrc; + + // 2. Fallback: compile the *committed* harness driver source on the fly (from .cs file). + // This builds a temp exe using the logic in the reviewed Program.cs; the test still + // drives the shipped Moq via LoadFrom + TryGet inside that driver. + // Only used if the separate project wasn't built in discoverable layout. + return CompileDriverFromCommittedSource(); + } + + static string CompileDriverFromCommittedSource() + { + var harnessSrc = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "..", "..", "src", "Moq.Tests.Issue1648Harness", "Program.cs")); + if (!File.Exists(harnessSrc)) + { + Assert.Fail("Committed harness driver source not found at expected location."); + return null; + } + + string moqRef = typeof(Moq.Async.AwaitableFactory).Assembly.Location; + + // Prefer a 4.5.x net4 ext for the driver's own reference (to simulate old version preload). + string extRef = null; + var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + var pref = Path.Combine(home, ".nuget", "packages", "system.threading.tasks.extensions", "4.5.4", "lib", "net461", "System.Threading.Tasks.Extensions.dll"); + if (File.Exists(pref)) extRef = pref; + if (extRef == null) + { + var any = Directory.GetFiles(Path.Combine(home, ".nuget", "packages", "system.threading.tasks.extensions"), "System.Threading.Tasks.Extensions.dll", SearchOption.AllDirectories) + .FirstOrDefault(p => p.Contains("net461") || p.Contains("net462")); + extRef = any; + } + if (extRef == null || !File.Exists(extRef)) + { + Assert.Fail("Could not find an older Tasks.Extensions for harness driver compilation."); + return null; + } + + string outExe = Path.Combine(Path.GetTempPath(), "Issue1648HarnessDriver_" + Guid.NewGuid().ToString("N") + ".exe"); + using (var provider = new CSharpCodeProvider()) + { + var cp = new CompilerParameters { GenerateExecutable = true, OutputAssembly = outExe, IncludeDebugInformation = false }; + cp.ReferencedAssemblies.Add(extRef); + cp.ReferencedAssemblies.Add(moqRef); + var cr = provider.CompileAssemblyFromFile(cp, harnessSrc); + if (cr.Errors.HasErrors) + { + string errs = string.Join("; ", cr.Errors.Cast().Select(e => e.ToString())); + Assert.Fail("Failed to compile committed harness driver: " + errs); + return null; + } + } + return Path.GetDirectoryName(outExe); + } + } + + #endregion + #region #159 public class _159 diff --git a/src/Moq/ActionObserver.cs b/src/Moq/ActionObserver.cs index 128dce6d1..6b8e9bb19 100644 --- a/src/Moq/ActionObserver.cs +++ b/src/Moq/ActionObserver.cs @@ -27,14 +27,14 @@ namespace Moq /// sealed class ActionObserver : ExpressionReconstructor { - public override Expression> ReconstructExpression(Action action, object[] ctorArgs = null) + public override Expression> ReconstructExpression(Action action, object?[]? ctorArgs = null) { using (var matcherObserver = MatcherObserver.Activate()) { // Create the root recording proxy: var root = (T)CreateProxy(typeof(T), ctorArgs, matcherObserver, out var rootRecorder); - Exception error = null; + Exception? error = null; try { // Execute the delegate. The root recorder will automatically "mock" return values @@ -61,7 +61,7 @@ public override Expression> ReconstructExpression(Action action, var invocation = recorder.Invocation; if (invocation != null) { - var resultType = invocation.Method.DeclaringType; + var resultType = invocation.Method.DeclaringType!; if (resultType.IsAssignableFrom(body.Type) == false) { if (AwaitableFactory.TryGet(body.Type) is { } awaitableHandler @@ -128,7 +128,7 @@ Expression[] GetArgumentExpressions(Invocation invocation, Match[] matches) // it will have left behind a `default(T)` argument, possibly coerced to the parameter type. // Therefore, we attempt to reproduce such coercions using `Convert.ChangeType`: Type defaultValueType = matches[matchIndex].RenderExpression.Type; - object defaultValue = defaultValueType.GetDefaultValue(); + object? defaultValue = defaultValueType.GetDefaultValue(); try { defaultValue = Convert.ChangeType(defaultValue, parameterTypes[argumentIndex]); @@ -178,7 +178,7 @@ Expression[] GetArgumentExpressions(Invocation invocation, Match[] matches) CultureInfo.CurrentCulture, Resources.MatcherAssignmentFailedDuringExpressionReconstruction, matches.Length, - $"{invocation.Method.DeclaringType.GetFormattedName()}.{invocation.Method.Name}")); + $"{invocation.Method.DeclaringType!.GetFormattedName()}.{invocation.Method.Name}")); } bool CanDistribute(int msi, int asi) @@ -228,10 +228,10 @@ bool CanDistribute(int msi, int asi) } // Creates a proxy (way more light-weight than a `Mock`!) with an invocation `Recorder` attached to it. - static IProxy CreateProxy(Type type, object[] ctorArgs, MatcherObserver matcherObserver, out Recorder recorder) + static IProxy CreateProxy(Type type, object?[]? ctorArgs, MatcherObserver matcherObserver, out Recorder recorder) { recorder = new Recorder(matcherObserver); - return (IProxy)ProxyFactory.Instance.CreateProxy(type, recorder, Type.EmptyTypes, ctorArgs ?? new object[0]); + return (IProxy)ProxyFactory.Instance.CreateProxy(type, recorder, Type.EmptyTypes, ctorArgs ?? new object?[0]); } @@ -241,9 +241,9 @@ sealed class Recorder : IInterceptor { readonly MatcherObserver matcherObserver; int creationTimestamp; - Invocation invocation; + Invocation? invocation; int invocationTimestamp; - object returnValue; + object? returnValue; public Recorder(MatcherObserver matcherObserver) { @@ -253,7 +253,7 @@ public Recorder(MatcherObserver matcherObserver) this.creationTimestamp = this.matcherObserver.GetNextTimestamp(); } - public Invocation Invocation => this.invocation; + public Invocation? Invocation => this.invocation; public IEnumerable Matches { @@ -264,7 +264,7 @@ public IEnumerable Matches } } - public Recorder Next => (Awaitable.TryGetResultRecursive(this.returnValue) as IProxy)?.Interceptor as Recorder; + public Recorder? Next => (Awaitable.TryGetResultRecursive(this.returnValue) as IProxy)?.Interceptor as Recorder; public void Intercept(Invocation invocation) { diff --git a/src/Moq/AsInterface.cs b/src/Moq/AsInterface.cs index db6b6636e..7ba906e03 100644 --- a/src/Moq/AsInterface.cs +++ b/src/Moq/AsInterface.cs @@ -19,9 +19,9 @@ public AsInterface(Mock owner) internal override List AdditionalInterfaces => this.owner.AdditionalInterfaces; - internal override Dictionary ConfiguredDefaultValues => this.owner.ConfiguredDefaultValues; + internal override Dictionary ConfiguredDefaultValues => this.owner.ConfiguredDefaultValues; - internal override object[] ConstructorArguments => this.owner.ConstructorArguments; + internal override object?[] ConstructorArguments => this.owner.ConstructorArguments; internal override InvocationCollection MutableInvocations => this.owner.MutableInvocations; @@ -49,7 +49,7 @@ public override DefaultValueProvider DefaultValueProvider public override TInterface Object { - get { return this.owner.Object as TInterface; } + get { return (TInterface)this.owner.Object; } } internal override SetupCollection MutableSetups => this.owner.MutableSetups; @@ -72,7 +72,7 @@ protected override object OnGetObject() public override string ToString() { - return this.owner.ToString(); + return this.owner.ToString()!; } } } diff --git a/src/Moq/Async/Awaitable.cs b/src/Moq/Async/Awaitable.cs index 192d64aea..9afe9df9b 100644 --- a/src/Moq/Async/Awaitable.cs +++ b/src/Moq/Async/Awaitable.cs @@ -14,7 +14,7 @@ static class Awaitable /// this method will return 42. /// /// The (possibly awaitable) object to be "unwrapped". - public static object TryGetResultRecursive(object obj) + public static object? TryGetResultRecursive(object? obj) { if (obj != null && AwaitableFactory.TryGet(obj.GetType()) is { } awaitableFactory diff --git a/src/Moq/Async/AwaitableFactory.cs b/src/Moq/Async/AwaitableFactory.cs index 8c51868bf..69e444c23 100644 --- a/src/Moq/Async/AwaitableFactory.cs +++ b/src/Moq/Async/AwaitableFactory.cs @@ -4,22 +4,67 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Reflection; using System.Threading.Tasks; namespace Moq.Async { static class AwaitableFactory + { +#if NET462 || NETSTANDARD2_0 + public static IAwaitableFactory? TryGet(Type type) => LegacyAwaitableFactory.TryGet(type); +#else + // Modern TFMs (ValueTask lives in platform BCL; direct, no static cctor, direct + // type checks for non-generic, simple creation for generics. This path performs + // no assembly name/FullName probing or deferred loading tricks. + // Non-VT (Task) path has no ValueTask tokens or VT-specific code in its IL when + // the helper is NoInlining. + public static IAwaitableFactory? TryGet(Type type) + { + Debug.Assert(type != null); + + if (type == typeof(Task)) + return TaskFactory.Instance; + + if (type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof(Task<>)) + return Create(typeof(TaskFactory<>), type); + + // VT handling isolated so common Task path never touches VT metadata at jit time for this method + return TryGetValueTask(type); + } + + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] + static IAwaitableFactory? TryGetValueTask(Type type) + { + if (type == typeof(ValueTask)) + return ValueTaskFactory.Instance; + + if (type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof(ValueTask<>)) + return Create(typeof(ValueTaskFactory<>), type); + + return null; + } + + static IAwaitableFactory Create(Type awaitableFactoryType, Type awaitableType) + { + return (IAwaitableFactory)Activator.CreateInstance( + awaitableFactoryType.MakeGenericType( + awaitableType.GetGenericArguments()))!; + } +#endif + } + +#if NET462 || NETSTANDARD2_0 + internal static class LegacyAwaitableFactory { static readonly Dictionary> Providers; - static AwaitableFactory() + static LegacyAwaitableFactory() { - AwaitableFactory.Providers = new Dictionary> + LegacyAwaitableFactory.Providers = new Dictionary> { [typeof(Task)] = awaitableType => TaskFactory.Instance, - [typeof(ValueTask)] = awaitableType => ValueTaskFactory.Instance, - [typeof(Task<>)] = awaitableType => AwaitableFactory.Create(typeof(TaskFactory<>), awaitableType), - [typeof(ValueTask<>)] = awaitableType => AwaitableFactory.Create(typeof(ValueTaskFactory<>), awaitableType), + [typeof(Task<>)] = awaitableType => LegacyAwaitableFactory.Create(typeof(TaskFactory<>), awaitableType), }; } @@ -27,7 +72,27 @@ static IAwaitableFactory Create(Type awaitableFactoryType, Type awaitableType) { return (IAwaitableFactory)Activator.CreateInstance( awaitableFactoryType.MakeGenericType( - awaitableType.GetGenericArguments())); + awaitableType.GetGenericArguments()))!; + } + + static IAwaitableFactory GetValueTaskFactory() + { + var asm = typeof(LegacyAwaitableFactory).Assembly; + var factoryType = asm.GetType("Moq.Async.ValueTaskFactory", throwOnError: true)!; + var instanceField = factoryType.GetField("Instance", BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly); + if (instanceField != null) + { + return (IAwaitableFactory)instanceField.GetValue(null)!; + } + return (IAwaitableFactory)Activator.CreateInstance(factoryType)!; + } + + static IAwaitableFactory GetValueTaskFactoryGeneric(Type awaitableType) + { + var asm = typeof(LegacyAwaitableFactory).Assembly; + var factoryType = asm.GetType("Moq.Async.ValueTaskFactory`1", throwOnError: true)!; + factoryType = factoryType.MakeGenericType(awaitableType.GetGenericArguments()); + return (IAwaitableFactory)Activator.CreateInstance(factoryType)!; } public static IAwaitableFactory? TryGet(Type type) @@ -36,12 +101,23 @@ static IAwaitableFactory Create(Type awaitableFactoryType, Type awaitableType) var key = type.IsConstructedGenericType ? type.GetGenericTypeDefinition() : type; - if (AwaitableFactory.Providers.TryGetValue(key, out var provider)) + if (LegacyAwaitableFactory.Providers.TryGetValue(key, out var provider)) { return provider.Invoke(type); } + var fullName = key.FullName; + if (fullName == "System.Threading.Tasks.ValueTask") + { + return GetValueTaskFactory(); + } + if (fullName == "System.Threading.Tasks.ValueTask`1") + { + return GetValueTaskFactoryGeneric(type); + } + return null; } } +#endif } diff --git a/src/Moq/Async/AwaitableFactory`1.cs b/src/Moq/Async/AwaitableFactory`1.cs index 3b88a9194..09dfda2aa 100644 --- a/src/Moq/Async/AwaitableFactory`1.cs +++ b/src/Moq/Async/AwaitableFactory`1.cs @@ -14,12 +14,13 @@ namespace Moq.Async /// for awaitables that do not produce a result when awaited. /// abstract class AwaitableFactory : IAwaitableFactory + where TAwaitable : notnull { Type IAwaitableFactory.ResultType => typeof(void); public abstract TAwaitable CreateCompleted(); - object IAwaitableFactory.CreateCompleted(object result) + object IAwaitableFactory.CreateCompleted(object? result) { Debug.Assert(result == null); @@ -50,7 +51,7 @@ Expression IAwaitableFactory.CreateResultExpression(Expression awaitableExpressi return new AwaitExpression(awaitableExpression, this); } - bool IAwaitableFactory.TryGetResult(object awaitable, out object result) + bool IAwaitableFactory.TryGetResult(object awaitable, out object? result) { Debug.Assert(awaitable is TAwaitable); diff --git a/src/Moq/Async/AwaitableFactory`2.cs b/src/Moq/Async/AwaitableFactory`2.cs index a0476053e..0931da051 100644 --- a/src/Moq/Async/AwaitableFactory`2.cs +++ b/src/Moq/Async/AwaitableFactory`2.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; @@ -14,16 +15,18 @@ namespace Moq.Async /// for awaitables that produce a result when awaited. /// abstract class AwaitableFactory : IAwaitableFactory + where TAwaitable : notnull { public Type ResultType => typeof(TResult); public abstract TAwaitable CreateCompleted(TResult result); - object IAwaitableFactory.CreateCompleted(object result) + object IAwaitableFactory.CreateCompleted(object? result) { + // TODO: result should only be null if TResult is a nullable type. Debug.Assert(result is TResult || result == null); - return this.CreateCompleted((TResult)result); + return this.CreateCompleted((TResult)result!); } public abstract TAwaitable CreateFaulted(Exception exception); @@ -45,11 +48,15 @@ object IAwaitableFactory.CreateFaulted(IEnumerable exceptions) return this.CreateFaulted(exceptions); } +#if NULLABLE_REFERENCE_TYPES + public abstract bool TryGetResult(TAwaitable awaitable, [MaybeNullWhen(false)] out TResult result); +#else public abstract bool TryGetResult(TAwaitable awaitable, out TResult result); +#endif public abstract Expression CreateResultExpression(Expression awaitableExpression); - bool IAwaitableFactory.TryGetResult(object awaitable, out object result) + bool IAwaitableFactory.TryGetResult(object awaitable, out object? result) { Debug.Assert(awaitable is TAwaitable); diff --git a/src/Moq/Async/IAwaitableFactory.cs b/src/Moq/Async/IAwaitableFactory.cs index 8af39e826..633cea616 100644 --- a/src/Moq/Async/IAwaitableFactory.cs +++ b/src/Moq/Async/IAwaitableFactory.cs @@ -11,7 +11,7 @@ interface IAwaitableFactory { Type ResultType { get; } - object CreateCompleted(object result = null); + object CreateCompleted(object? result = null); object CreateFaulted(Exception exception); @@ -19,6 +19,6 @@ interface IAwaitableFactory Expression CreateResultExpression(Expression awaitableExpression); - bool TryGetResult(object awaitable, out object result); + bool TryGetResult(object awaitable, out object? result); } } diff --git a/src/Moq/Async/TaskFactory.cs b/src/Moq/Async/TaskFactory.cs index d43276181..303f7f820 100644 --- a/src/Moq/Async/TaskFactory.cs +++ b/src/Moq/Async/TaskFactory.cs @@ -19,7 +19,7 @@ sealed class TaskFactory : AwaitableFactory public override Task CreateCompleted() { - return Task.FromResult(default); + return Task.CompletedTask; } public override Task CreateFaulted(Exception exception) diff --git a/src/Moq/Async/TaskFactory`1.cs b/src/Moq/Async/TaskFactory`1.cs index 1c419e6f1..1baefd3ae 100644 --- a/src/Moq/Async/TaskFactory`1.cs +++ b/src/Moq/Async/TaskFactory`1.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using System.Threading.Tasks; @@ -33,10 +34,14 @@ public override Expression CreateResultExpression(Expression awaitableExpression { return Expression.MakeMemberAccess( awaitableExpression, - typeof(Task).GetProperty(nameof(Task.Result))); + typeof(Task).GetProperty(nameof(Task.Result))!); } - public override bool TryGetResult(Task task, out TResult result) +#if NULLABLE_REFERENCE_TYPES + public override bool TryGetResult(Task task, [MaybeNullWhen(false)] out TResult result) +#else + public override bool TryGetResult(Task task, out TResult? result) +#endif { if (task.Status == TaskStatus.RanToCompletion) { diff --git a/src/Moq/Async/ValueTaskFactory`1.cs b/src/Moq/Async/ValueTaskFactory`1.cs index 53eaa60ed..e14ddca67 100644 --- a/src/Moq/Async/ValueTaskFactory`1.cs +++ b/src/Moq/Async/ValueTaskFactory`1.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using System.Threading.Tasks; @@ -33,10 +34,14 @@ public override Expression CreateResultExpression(Expression awaitableExpression { return Expression.MakeMemberAccess( awaitableExpression, - typeof(ValueTask).GetProperty(nameof(ValueTask.Result))); + typeof(ValueTask).GetProperty(nameof(ValueTask.Result))!); } +#if NULLABLE_REFERENCE_TYPES + public override bool TryGetResult(ValueTask valueTask, [MaybeNullWhen(false)] out TResult result) +#else public override bool TryGetResult(ValueTask valueTask, out TResult result) +#endif { if (valueTask.IsCompletedSuccessfully) { diff --git a/src/Moq/Behaviors/RaiseEvent.cs b/src/Moq/Behaviors/RaiseEvent.cs index 8707b00ea..556f5d370 100644 --- a/src/Moq/Behaviors/RaiseEvent.cs +++ b/src/Moq/Behaviors/RaiseEvent.cs @@ -11,10 +11,10 @@ sealed class RaiseEvent : Behavior { Mock mock; LambdaExpression expression; - Delegate eventArgsFunc; - object[] eventArgsParams; + Delegate? eventArgsFunc; + object?[]? eventArgsParams; - public RaiseEvent(Mock mock, LambdaExpression expression, Delegate eventArgsFunc, object[] eventArgsParams) + public RaiseEvent(Mock mock, LambdaExpression expression, Delegate? eventArgsFunc, object?[]? eventArgsParams) { Debug.Assert(mock != null); Debug.Assert(expression != null); @@ -28,7 +28,7 @@ public RaiseEvent(Mock mock, LambdaExpression expression, Delegate eventArgsFunc public override void Execute(Invocation invocation) { - object[] args; + object?[] args; if (this.eventArgsParams != null) { @@ -36,14 +36,14 @@ public override void Execute(Invocation invocation) } else { - var argsFuncType = this.eventArgsFunc.GetType(); + var argsFuncType = this.eventArgsFunc!.GetType(); if (argsFuncType.IsGenericType && argsFuncType.GetGenericArguments().Length == 1) { - args = new object[] { this.mock.Object, this.eventArgsFunc.InvokePreserveStack() }; + args = new object?[] { this.mock.Object, this.eventArgsFunc.InvokePreserveStack() }; } else { - args = new object[] { this.mock.Object, this.eventArgsFunc.InvokePreserveStack(invocation.Arguments) }; + args = new object?[] { this.mock.Object, this.eventArgsFunc.InvokePreserveStack(invocation.Arguments) }; } } diff --git a/src/Moq/Behaviors/ReturnBaseOrDefaultValue.cs b/src/Moq/Behaviors/ReturnBaseOrDefaultValue.cs index 20b186817..7d604b96c 100644 --- a/src/Moq/Behaviors/ReturnBaseOrDefaultValue.cs +++ b/src/Moq/Behaviors/ReturnBaseOrDefaultValue.cs @@ -31,7 +31,7 @@ public override void Execute(Invocation invocation) var tryCallDefaultInterfaceImplementation = false; #endif - var declaringType = method.DeclaringType; + var declaringType = method.DeclaringType!; if (declaringType.IsInterface) { if (this.mock.MockedType.IsInterface) diff --git a/src/Moq/Behaviors/ReturnComputedValue.cs b/src/Moq/Behaviors/ReturnComputedValue.cs index 750debe60..363a2baf8 100644 --- a/src/Moq/Behaviors/ReturnComputedValue.cs +++ b/src/Moq/Behaviors/ReturnComputedValue.cs @@ -8,9 +8,9 @@ namespace Moq.Behaviors { sealed class ReturnComputedValue : Behavior { - readonly Func valueFactory; + readonly Func valueFactory; - public ReturnComputedValue(Func valueFactory) + public ReturnComputedValue(Func valueFactory) { Debug.Assert(valueFactory != null); diff --git a/src/Moq/Behaviors/ReturnValue.cs b/src/Moq/Behaviors/ReturnValue.cs index 8824ecd20..97c2ca601 100644 --- a/src/Moq/Behaviors/ReturnValue.cs +++ b/src/Moq/Behaviors/ReturnValue.cs @@ -5,14 +5,14 @@ namespace Moq.Behaviors { sealed class ReturnValue : Behavior { - readonly object value; + readonly object? value; - public ReturnValue(object value) + public ReturnValue(object? value) { this.value = value; } - public object Value => this.value; + public object? Value => this.value; public override void Execute(Invocation invocation) { diff --git a/src/Moq/Behaviors/ThrowComputedException.cs b/src/Moq/Behaviors/ThrowComputedException.cs index 8afdc72c7..b1404d991 100644 --- a/src/Moq/Behaviors/ThrowComputedException.cs +++ b/src/Moq/Behaviors/ThrowComputedException.cs @@ -8,9 +8,9 @@ namespace Moq.Behaviors { sealed class ThrowComputedException : Behavior { - readonly Func exceptionFactory; + readonly Func exceptionFactory; - public ThrowComputedException(Func exceptionFactory) + public ThrowComputedException(Func exceptionFactory) { Debug.Assert(exceptionFactory != null); @@ -19,6 +19,7 @@ public ThrowComputedException(Func exceptionFactory) public override void Execute(Invocation invocation) { + // TODO: Technically this permits `throw null` here. throw this.exceptionFactory.Invoke(invocation); } } diff --git a/src/Moq/Capture.cs b/src/Moq/Capture.cs index 0798bc94e..fd04d1441 100644 --- a/src/Moq/Capture.cs +++ b/src/Moq/Capture.cs @@ -76,7 +76,7 @@ public static T In(IList collection, Expression> predicate) public static T With(CaptureMatch match) { Match.Register(match); - return default(T); + return default(T)!; } } } diff --git a/src/Moq/Condition.cs b/src/Moq/Condition.cs index 70ab3b434..c245974a8 100644 --- a/src/Moq/Condition.cs +++ b/src/Moq/Condition.cs @@ -8,15 +8,15 @@ namespace Moq sealed class Condition { Func condition; - Action success; + Action? success; - public Condition(Func condition, Action success = null) + public Condition(Func condition, Action? success = null) { this.condition = condition; this.success = success; } - public bool IsTrue => this.condition?.Invoke() == true; + public bool IsTrue => this.condition.Invoke(); public void SetupEvaluatedSuccessfully() => this.success?.Invoke(); } diff --git a/src/Moq/DefaultValueProvider.cs b/src/Moq/DefaultValueProvider.cs index c2bb2b34b..290da47db 100644 --- a/src/Moq/DefaultValueProvider.cs +++ b/src/Moq/DefaultValueProvider.cs @@ -49,7 +49,7 @@ protected DefaultValueProvider() /// /// Implementations may assume that all parameters have valid, non-, non- values. /// - protected internal abstract object GetDefaultValue(Type type, Mock mock); + protected internal abstract object? GetDefaultValue(Type type, Mock mock); /// /// @@ -65,7 +65,7 @@ protected DefaultValueProvider() /// /// Implementations may assume that all parameters have valid, non-, non- values. /// - protected internal virtual object GetDefaultParameterValue(ParameterInfo parameter, Mock mock) + protected internal virtual object? GetDefaultParameterValue(ParameterInfo parameter, Mock mock) { Debug.Assert(parameter != null); Debug.Assert(parameter.ParameterType != typeof(void)); @@ -88,7 +88,7 @@ protected internal virtual object GetDefaultParameterValue(ParameterInfo paramet /// /// Implementations may assume that all parameters have valid, non-, non- values. /// - protected internal virtual object GetDefaultReturnValue(MethodInfo method, Mock mock) + protected internal virtual object? GetDefaultReturnValue(MethodInfo method, Mock mock) { Debug.Assert(method != null); Debug.Assert(method.ReturnType != typeof(void)); diff --git a/src/Moq/EmptyDefaultValueProvider.cs b/src/Moq/EmptyDefaultValueProvider.cs index 3f6e05242..42f282af8 100644 --- a/src/Moq/EmptyDefaultValueProvider.cs +++ b/src/Moq/EmptyDefaultValueProvider.cs @@ -28,7 +28,7 @@ internal EmptyDefaultValueProvider() static object CreateArray(Type type, Mock mock) { - var elementType = type.GetElementType(); + var elementType = type.GetElementType()!; var lengths = new int[type.GetArrayRank()]; return Array.CreateInstance(elementType, lengths); } @@ -57,7 +57,7 @@ static object CreateQueryableOf(Type type, Mock mock) return typeof(Queryable).GetMethods("AsQueryable") .Single(x => x.IsGenericMethod) .MakeGenericMethod(elementType) - .Invoke(null, new[] { array }); + .Invoke(null, new[] { array })!; } } } diff --git a/src/Moq/Evaluator.cs b/src/Moq/Evaluator.cs index fd9614a90..4ce3c4681 100644 --- a/src/Moq/Evaluator.cs +++ b/src/Moq/Evaluator.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; namespace Moq @@ -53,7 +54,10 @@ internal Expression Eval(Expression exp) return this.Visit(exp); } - public override Expression Visit(Expression exp) +#if NULLABLE_REFERENCE_TYPES + [return: NotNullIfNotNull("exp")] +#endif + public override Expression? Visit(Expression? exp) { if (exp == null) { @@ -85,7 +89,7 @@ static Expression Evaluate(Expression e) class Nominator : ExpressionVisitor { Func fnCanBeEvaluated; - HashSet candidates; + HashSet candidates = null!; bool cannotBeEvaluated; internal Nominator(Func fnCanBeEvaluated) @@ -100,7 +104,7 @@ internal HashSet Nominate(Expression expression) return this.candidates; } - public override Expression Visit(Expression expression) + public override Expression? Visit(Expression? expression) { if (expression != null && expression.NodeType != ExpressionType.Quote) { diff --git a/src/Moq/EventHandlerCollection.cs b/src/Moq/EventHandlerCollection.cs index ceb7be911..35749ec2f 100644 --- a/src/Moq/EventHandlerCollection.cs +++ b/src/Moq/EventHandlerCollection.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Reflection; namespace Moq @@ -36,19 +37,31 @@ public void Remove(EventInfo @event, Delegate eventHandler) { lock (this.eventHandlers) { - this.eventHandlers[@event] = Delegate.Remove(this.TryGet(@event), eventHandler); + var resultingDelegate = Delegate.Remove(this.TryGet(@event), eventHandler); + if (resultingDelegate == null) + { + eventHandlers.Remove(@event); + } + else + { + eventHandlers[@event] = resultingDelegate; + } } } - public bool TryGet(EventInfo @event, out Delegate handlers) +#if NULLABLE_REFERENCE_TYPES + public bool TryGet(EventInfo @event, [NotNullWhen(true)] out Delegate? handlers) +#else + public bool TryGet(EventInfo @event, out Delegate? handlers) +#endif { lock (this.eventHandlers) { - return this.eventHandlers.TryGetValue(@event, out handlers) && handlers != null; + return this.eventHandlers.TryGetValue(@event, out handlers); } } - Delegate TryGet(EventInfo @event) + Delegate? TryGet(EventInfo @event) { return this.eventHandlers.TryGetValue(@event, out var handlers) ? handlers : null; } diff --git a/src/Moq/Expectation.cs b/src/Moq/Expectation.cs index 7557a7e37..c69f80533 100644 --- a/src/Moq/Expectation.cs +++ b/src/Moq/Expectation.cs @@ -2,6 +2,7 @@ // All rights reserved. Licensed under the BSD 3-Clause License; see License.txt. using System; +using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using Moq.Async; @@ -16,18 +17,23 @@ abstract class Expectation : IEquatable { public abstract LambdaExpression Expression { get; } - public virtual bool HasResultExpression(out IAwaitableFactory awaitableFactory) +#if NULLABLE_REFERENCE_TYPES + + public virtual bool HasResultExpression([NotNullWhen(true)] out IAwaitableFactory? awaitableFactory) +#else + public virtual bool HasResultExpression(out IAwaitableFactory? awaitableFactory) +#endif { awaitableFactory = null; return false; } - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is Expectation other && this.Equals(other); } - public abstract bool Equals(Expectation other); + public abstract bool Equals(Expectation? other); public abstract override int GetHashCode(); diff --git a/src/Moq/ExpressionComparer.cs b/src/Moq/ExpressionComparer.cs index 940089a5f..3400f098d 100644 --- a/src/Moq/ExpressionComparer.cs +++ b/src/Moq/ExpressionComparer.cs @@ -21,7 +21,7 @@ sealed class ExpressionComparer : IEqualityComparer { } - public bool Equals(Expression x, Expression y) + public bool Equals(Expression? x, Expression? y) { if (object.ReferenceEquals(x, y)) { @@ -137,7 +137,7 @@ public bool Equals(Expression x, Expression y) public int GetHashCode(Expression obj) { - return obj == null ? 0 : obj.GetHashCode(); + return obj.GetHashCode(); } static bool Equals(ReadOnlyCollection x, ReadOnlyCollection y, Func comparer) diff --git a/src/Moq/ExpressionExtensions.cs b/src/Moq/ExpressionExtensions.cs index 1b2b324af..805a39272 100644 --- a/src/Moq/ExpressionExtensions.cs +++ b/src/Moq/ExpressionExtensions.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Linq.Expressions; @@ -64,7 +65,11 @@ internal static TDelegate CompileUsingExpressionCompiler(this Express return ExpressionCompiler.Instance.Compile(expression); } - public static bool IsMatch(this Expression expression, out Match match) +#if NULLABLE_REFERENCE_TYPES + public static bool IsMatch(this Expression expression, [NotNullWhen(true)] out Match? match) +#else + public static bool IsMatch(this Expression expression, out Match? match) +#endif { if (expression is MatchExpression matchExpression) { @@ -262,6 +267,7 @@ void Split(Expression e, out Expression r /* remainder */, out MethodExpectation } else // This should be unreachable. { + // TODO: Should we throw here? method = null; } p = new MethodExpectation( @@ -286,7 +292,7 @@ void Split(Expression e, out Expression r /* remainder */, out MethodExpectation expression: Expression.Lambda( Expression.Invoke(parameter, arguments), parameter), - method: r.Type.GetMethod("Invoke", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance), + method: r.Type.GetMethod("Invoke", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)!, arguments); return; } @@ -321,7 +327,8 @@ void Split(Expression e, out Expression r /* remainder */, out MethodExpectation } else // This should be unreachable. { - method = null; + // TODO: Should we throw here? + method = null!; } p = new MethodExpectation( expression: Expression.Lambda( @@ -341,7 +348,7 @@ void Split(Expression e, out Expression r /* remainder */, out MethodExpectation bool IsResult(MemberInfo member, out IAwaitableFactory? awaitableFactory) { - var instanceType = member.DeclaringType; + var instanceType = member.DeclaringType!; awaitableFactory = AwaitableFactory.TryGet(instanceType); var returnType = member switch { @@ -363,7 +370,7 @@ internal static PropertyInfo GetReboundProperty(this MemberExpression expression // the expression. we attempt to correct this here by checking whether the type of the accessed object // has a property by the same name whose base definition equals the property in the expression; if so, // we "upgrade" to the derived property. - if (property.DeclaringType != expression.Expression.Type) + if (property.DeclaringType != expression.Expression!.Type) { var parameterTypes = new ParameterTypes(property.GetIndexParameters()); var derivedProperty = expression.Expression.Type diff --git a/src/Moq/ExpressionReconstructor.cs b/src/Moq/ExpressionReconstructor.cs index 27a9f529c..196fc8209 100644 --- a/src/Moq/ExpressionReconstructor.cs +++ b/src/Moq/ExpressionReconstructor.cs @@ -29,6 +29,6 @@ protected ExpressionReconstructor() /// /// The delegate for which to reconstruct a LINQ expression tree. /// Arguments to pass to a parameterized constructor of . (Optional.) - public abstract Expression> ReconstructExpression(Action action, object[] ctorArgs = null); + public abstract Expression> ReconstructExpression(Action action, object?[]? ctorArgs = null); } } diff --git a/src/Moq/Expressions/Visitors/ConstructorCallVisitor.cs b/src/Moq/Expressions/Visitors/ConstructorCallVisitor.cs index 1a4917138..627792521 100644 --- a/src/Moq/Expressions/Visitors/ConstructorCallVisitor.cs +++ b/src/Moq/Expressions/Visitors/ConstructorCallVisitor.cs @@ -2,6 +2,7 @@ // All rights reserved. Licensed under the BSD 3-Clause License; see License.txt. using System; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Linq.Expressions; @@ -36,13 +37,18 @@ public static object[] ExtractArgumentValues(LambdaExpression newExpression) return visitor.arguments; } - ConstructorInfo constructor; + ConstructorInfo? constructor; object[] arguments; - public override Expression Visit(Expression node) +#if NULLABLE_REFERENCE_TYPES + [return: NotNullIfNotNull("node")] +#endif + public override Expression? Visit(Expression? node) { - switch (node.NodeType) + switch (node?.NodeType) { + case null: + return null; case ExpressionType.Lambda: case ExpressionType.New: case ExpressionType.Quote: @@ -58,18 +64,15 @@ public override Expression Visit(Expression node) protected override Expression VisitNew(NewExpression node) { - if (node != null) - { - constructor = node.Constructor; + constructor = node.Constructor; - // Creates a lambda which uses the same argument expressions as the - // arguments contained in the NewExpression - var argumentExtractor = Expression.Lambda>( - Expression.NewArrayInit( - typeof(object), - node.Arguments.Select(a => Expression.Convert(a, typeof(object))))); - arguments = ExpressionCompiler.Instance.Compile(argumentExtractor).Invoke(); - } + // Creates a lambda which uses the same argument expressions as the + // arguments contained in the NewExpression + var argumentExtractor = Expression.Lambda>( + Expression.NewArrayInit( + typeof(object), + node.Arguments.Select(a => Expression.Convert(a, typeof(object))))); + arguments = ExpressionCompiler.Instance.Compile(argumentExtractor).Invoke(); return node; } } diff --git a/src/Moq/Expressions/Visitors/EvaluateCaptures.cs b/src/Moq/Expressions/Visitors/EvaluateCaptures.cs index 594729560..b44040be1 100644 --- a/src/Moq/Expressions/Visitors/EvaluateCaptures.cs +++ b/src/Moq/Expressions/Visitors/EvaluateCaptures.cs @@ -22,7 +22,7 @@ protected override Expression VisitMember(MemberExpression node) { if (node.Member is FieldInfo fi && node.Expression is ConstantExpression ce - && node.Member.DeclaringType.IsDefined(typeof(CompilerGeneratedAttribute))) + && fi.DeclaringType!.IsDefined(typeof(CompilerGeneratedAttribute))) { return Expression.Constant(fi.GetValue(ce.Value), node.Type); } diff --git a/src/Moq/Expressions/Visitors/UpgradePropertyAccessorMethods.cs b/src/Moq/Expressions/Visitors/UpgradePropertyAccessorMethods.cs index 62554952d..05d0645b9 100644 --- a/src/Moq/Expressions/Visitors/UpgradePropertyAccessorMethods.cs +++ b/src/Moq/Expressions/Visitors/UpgradePropertyAccessorMethods.cs @@ -48,7 +48,7 @@ protected override Expression VisitMethodCall(MethodCallExpression node) if (argumentCount == 0) { // getter: - var property = node.Method.DeclaringType.GetProperty(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + var property = node.Method.DeclaringType!.GetProperty(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); Debug.Assert(property != null && property.GetGetMethod(true) == node.Method); return Expression.MakeMemberAccess(instance, property); @@ -58,10 +58,10 @@ protected override Expression VisitMethodCall(MethodCallExpression node) // indexer getter: var parameterTypes = node.Method.GetParameterTypes(); var argumentTypes = parameterTypes.ToArray(); - var indexer = node.Method.DeclaringType.GetProperty(name, node.Method.ReturnType, argumentTypes); + var indexer = node.Method.DeclaringType!.GetProperty(name, node.Method.ReturnType, argumentTypes); Debug.Assert(indexer != null && indexer.GetGetMethod(true) == node.Method); - return Expression.MakeIndex(instance, indexer, arguments); + return Expression.MakeIndex(instance!, indexer, arguments); } } else if (node.Method.IsSetAccessor()) @@ -72,7 +72,7 @@ protected override Expression VisitMethodCall(MethodCallExpression node) if (argumentCount == 1) { // setter: - var property = node.Method.DeclaringType.GetProperty(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + var property = node.Method.DeclaringType!.GetProperty(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); Debug.Assert(property != null && property.GetSetMethod(true) == node.Method); var value = node.Arguments[0]; @@ -83,12 +83,12 @@ protected override Expression VisitMethodCall(MethodCallExpression node) // indexer setter: var parameterTypes = node.Method.GetParameterTypes(); var argumentTypes = parameterTypes.Take(parameterTypes.Count - 1).ToArray(); - var indexer = node.Method.DeclaringType.GetProperty(name, parameterTypes.Last(), argumentTypes); + var indexer = node.Method.DeclaringType!.GetProperty(name, parameterTypes.Last(), argumentTypes); Debug.Assert(indexer != null && indexer.GetSetMethod(true) == node.Method); var indices = arguments.Take(argumentCount - 1); var value = arguments.Last(); - return Expression.Assign(Expression.MakeIndex(instance, indexer, indices), value); + return Expression.Assign(Expression.MakeIndex(instance!, indexer, indices), value); } } } diff --git a/src/Moq/Extensions.cs b/src/Moq/Extensions.cs index 0a920c72f..a33b61541 100644 --- a/src/Moq/Extensions.cs +++ b/src/Moq/Extensions.cs @@ -5,6 +5,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; @@ -20,12 +21,20 @@ public static bool CanCreateInstance(this Type type) return type.IsValueType || type.GetConstructor(Type.EmptyTypes) != null; } - public static bool CanRead(this PropertyInfo property, out MethodInfo getter) +#if NULLABLE_REFERENCE_TYPES + public static bool CanRead(this PropertyInfo property, [NotNullWhen(true)] out MethodInfo? getter) +#else + public static bool CanRead(this PropertyInfo property, out MethodInfo? getter) +#endif { return property.CanRead(out getter, out _); } - public static bool CanRead(this PropertyInfo property, out MethodInfo getter, out PropertyInfo getterProperty) +#if NULLABLE_REFERENCE_TYPES + public static bool CanRead(this PropertyInfo property, [NotNullWhen(true)] out MethodInfo? getter, [NotNullWhen(true)] out PropertyInfo? getterProperty) +#else + public static bool CanRead(this PropertyInfo property, out MethodInfo? getter, out PropertyInfo? getterProperty) +#endif { if (property.CanRead) { @@ -51,7 +60,7 @@ public static bool CanRead(this PropertyInfo property, out MethodInfo getter, ou var baseProperty = baseSetter .DeclaringType - .GetMember(property.Name, MemberTypes.Property, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + !.GetMember(property.Name, MemberTypes.Property, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) .Cast() .First(p => p.GetSetMethod(nonPublic: true) == baseSetter); return baseProperty.CanRead(out getter, out getterProperty); @@ -63,12 +72,21 @@ public static bool CanRead(this PropertyInfo property, out MethodInfo getter, ou return false; } - public static bool CanWrite(this PropertyInfo property, out MethodInfo setter) +#if NULLABLE_REFERENCE_TYPES + public static bool CanWrite(this PropertyInfo property, [NotNullWhen(true)] out MethodInfo? setter) +#else + public static bool CanWrite(this PropertyInfo property, out MethodInfo? setter) +#endif { return property.CanWrite(out setter, out _); } - public static bool CanWrite(this PropertyInfo property, out MethodInfo setter, out PropertyInfo setterProperty) +#if NULLABLE_REFERENCE_TYPES + public static bool CanWrite(this PropertyInfo property, [NotNullWhen(true)] out MethodInfo? setter, [NotNullWhen(true)] out PropertyInfo? setterProperty) + +#else + public static bool CanWrite(this PropertyInfo property, out MethodInfo? setter, out PropertyInfo? setterProperty) +#endif { if (property.CanWrite) { @@ -94,7 +112,7 @@ public static bool CanWrite(this PropertyInfo property, out MethodInfo setter, o var baseProperty = baseGetter .DeclaringType - .GetMember(property.Name, MemberTypes.Property, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + !.GetMember(property.Name, MemberTypes.Property, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) .Cast() .First(p => p.GetGetMethod(nonPublic: true) == baseGetter); return baseProperty.CanWrite(out setter, out setterProperty); @@ -109,7 +127,7 @@ public static bool CanWrite(this PropertyInfo property, out MethodInfo setter, o /// /// Gets the default value for the specified type. This is the Reflection counterpart of C#'s operator. /// - public static object GetDefaultValue(this Type type) + public static object? GetDefaultValue(this Type type) { return type.IsValueType ? Activator.CreateInstance(type) : null; } @@ -129,20 +147,20 @@ public static MethodInfo GetImplementingMethod(this MethodInfo method, Type prox method = method.GetGenericMethodDefinition(); } - var declaringType = method.DeclaringType; + var declaringType = method.DeclaringType!; if (declaringType.IsInterface) { Debug.Assert(declaringType.IsAssignableFrom(proxyType)); - var map = GetInterfaceMap(proxyType, method.DeclaringType); + var map = GetInterfaceMap(proxyType, declaringType); var index = Array.IndexOf(map.InterfaceMethods, method); Debug.Assert(index >= 0); return map.TargetMethods[index].GetBaseDefinition(); } else if (declaringType.IsDelegateType()) { - return proxyType.GetMethod("Invoke"); + return proxyType.GetMethod("Invoke")!; } else { @@ -152,15 +170,15 @@ public static MethodInfo GetImplementingMethod(this MethodInfo method, Type prox } } - public static object InvokePreserveStack(this Delegate del, IReadOnlyList args = null) + public static object? InvokePreserveStack(this Delegate del, IReadOnlyList? args = null) { try { - return del.DynamicInvoke((args as object[]) ?? args?.ToArray()); + return del.DynamicInvoke((args as object?[]) ?? args?.ToArray()); } catch (TargetInvocationException ex) { - ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); + ExceptionDispatchInfo.Capture(ex.InnerException!).Throw(); throw; } } @@ -237,11 +255,15 @@ public static bool IsTypeMatcher(this Type type) return Attribute.IsDefined(type, typeof(TypeMatcherAttribute)); } - public static bool IsTypeMatcher(this Type type, out Type typeMatcherType) +#if NULLABLE_REFERENCE_TYPES + public static bool IsTypeMatcher(this Type type, [NotNullWhen(true)] out Type? typeMatcherType) +#else + public static bool IsTypeMatcher(this Type type, out Type? typeMatcherType) +#endif { if (type.IsTypeMatcher()) { - var attr = (TypeMatcherAttribute)Attribute.GetCustomAttribute(type, typeof(TypeMatcherAttribute)); + var attr = (TypeMatcherAttribute)Attribute.GetCustomAttribute(type, typeof(TypeMatcherAttribute))!; typeMatcherType = attr.Type ?? type; Guard.ImplementsTypeMatcherProtocol(typeMatcherType); return true; @@ -261,7 +283,7 @@ public static bool IsOrContainsTypeMatcher(this Type type) } else if (type.HasElementType) { - return type.GetElementType().IsOrContainsTypeMatcher(); + return type.GetElementType()!.IsOrContainsTypeMatcher(); } else if (type.IsGenericType) { @@ -373,7 +395,7 @@ public static bool CompareParameterTypesTo(this Delegate function, return false; } - static MethodInfo GetInvokeMethodFromUntypedDelegateCallback(Delegate callback) + static MethodInfo? GetInvokeMethodFromUntypedDelegateCallback(Delegate callback) { // Section 8.9.3 of 4th Ed ECMA 335 CLI spec requires delegates to have an 'Invoke' method. // However, there is not a requirement for 'public', or for it to be unambiguous. @@ -406,7 +428,7 @@ public static Type SubstituteTypeMatchers(this Type type, Type other) if (type.IsTypeMatcher(out var typeMatcherType)) { - var typeMatcher = (ITypeMatcher)Activator.CreateInstance(typeMatcherType); + var typeMatcher = (ITypeMatcher)Activator.CreateInstance(typeMatcherType)!; if (typeMatcher.Matches(other)) { @@ -415,8 +437,8 @@ public static Type SubstituteTypeMatchers(this Type type, Type other) } else if (type.HasElementType && other.HasElementType) { - var te = type.GetElementType(); - var oe = other.GetElementType(); + var te = type.GetElementType()!; + var oe = other.GetElementType()!; if (type.IsArray && other.IsArray) { @@ -487,11 +509,10 @@ static InterfaceMapping GetInterfaceMap(Type type, Type interfaceType) public static IEnumerable FindAllInnerMocks(this SetupCollection setups) { return setups.FindAll(setup => !setup.IsConditional) - .SelectMany(setup => setup.InnerMocks) - .Where(innerMock => innerMock != null); + .SelectMany(setup => setup.InnerMocks); } - public static Mock FindLastInnerMock(this SetupCollection setups, Func predicate) + public static Mock? FindLastInnerMock(this SetupCollection setups, Func predicate) { return setups.FindLast(setup => !setup.IsConditional && predicate(setup))?.InnerMocks.SingleOrDefault(); } diff --git a/src/Moq/Guard.cs b/src/Moq/Guard.cs index 77d658769..647c11dc1 100644 --- a/src/Moq/Guard.cs +++ b/src/Moq/Guard.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq.Expressions; using System.Reflection; @@ -28,7 +29,7 @@ public static void CanCreateInstance(Type type) } } - public static void ImplementsInterface(Type interfaceType, Type type, string paramName = null) + public static void ImplementsInterface(Type interfaceType, Type type, string? paramName = null) { Debug.Assert(interfaceType != null); Debug.Assert(interfaceType.IsInterface); @@ -92,7 +93,7 @@ public static void IsOverridable(MethodInfo method, Expression expression) string.Format( CultureInfo.CurrentCulture, method.IsExtensionMethod() ? Resources.UnsupportedExtensionMethod : Resources.UnsupportedStaticMember, - $"{method.DeclaringType.GetFormattedName()}.{method.Name}"))); + $"{method.DeclaringType!.GetFormattedName()}.{method.Name}"))); } else if (!method.CanOverride()) { @@ -104,18 +105,18 @@ public static void IsOverridable(MethodInfo method, Expression expression) string.Format( CultureInfo.CurrentCulture, Resources.UnsupportedNonOverridableMember, - $"{method.DeclaringType.GetFormattedName()}.{method.Name}"))); + $"{method.DeclaringType!.GetFormattedName()}.{method.Name}"))); } } public static void IsVisibleToProxyFactory(MethodInfo method) { - if (ProxyFactory.Instance.IsMethodVisible(method, out string messageIfNotVisible) == false) + if (ProxyFactory.Instance.IsMethodVisible(method, out string? messageIfNotVisible) == false) { throw new ArgumentException(string.Format( CultureInfo.CurrentCulture, Resources.MethodNotVisibleToProxyFactory, - method.DeclaringType.Name, + method.DeclaringType!.Name, method.Name, messageIfNotVisible)); } @@ -165,7 +166,11 @@ public static void IsEventRemove(LambdaExpression expression, string paramName) /// Ensures the given is not null. /// Throws otherwise. /// - public static void NotNull(object value, string paramName) +#if NULLABLE_REFERENCE_TYPES + public static void NotNull([NotNull] object? value, string paramName) +#else + public static void NotNull(object? value, string paramName) +#endif { if (value == null) { @@ -226,7 +231,7 @@ public static void CanRead(PropertyInfo property) throw new ArgumentException(string.Format( CultureInfo.CurrentCulture, Resources.PropertyGetNotFound, - property.DeclaringType.Name, property.Name)); + property.DeclaringType!.Name, property.Name)); } } @@ -237,7 +242,7 @@ public static void CanWrite(PropertyInfo property) throw new ArgumentException(string.Format( CultureInfo.CurrentCulture, Resources.PropertySetNotFound, - property.DeclaringType.Name, property.Name)); + property.DeclaringType!.Name, property.Name)); } } } diff --git a/src/Moq/IInvocation.cs b/src/Moq/IInvocation.cs index c2982b920..f543b5f26 100644 --- a/src/Moq/IInvocation.cs +++ b/src/Moq/IInvocation.cs @@ -20,12 +20,12 @@ public interface IInvocation /// /// Gets the arguments of the invocation. /// - IReadOnlyList Arguments { get; } + IReadOnlyList Arguments { get; } /// /// Gets the setup that matched this invocation (or if there was no matching setup). /// - ISetup MatchingSetup { get; } + ISetup? MatchingSetup { get; } /// /// Gets whether this invocation was successfully verified by any of the various `Verify` methods. @@ -35,11 +35,11 @@ public interface IInvocation /// /// The value being returned for a non-void method if no exception was thrown. /// - object ReturnValue { get; } + object? ReturnValue { get; } /// /// Optional exception if the method invocation results in an exception being thrown. /// - Exception Exception { get; } + Exception? Exception { get; } } } \ No newline at end of file diff --git a/src/Moq/IMatcher.cs b/src/Moq/IMatcher.cs index 1cd0dceef..e32c55ad5 100644 --- a/src/Moq/IMatcher.cs +++ b/src/Moq/IMatcher.cs @@ -7,8 +7,8 @@ namespace Moq { interface IMatcher { - bool Matches(object argument, Type parameterType); + bool Matches(object? argument, Type parameterType); - void SetupEvaluatedSuccessfully(object argument, Type parameterType); + void SetupEvaluatedSuccessfully(object? argument, Type parameterType); } } diff --git a/src/Moq/ISetup.cs b/src/Moq/ISetup.cs index 03255edba..9defbc549 100644 --- a/src/Moq/ISetup.cs +++ b/src/Moq/ISetup.cs @@ -49,7 +49,7 @@ public interface ISetup // /// The setup has more than one inner mock. // [Obsolete("Use 'InnerMocks' instead.")] // [EditorBrowsable(EditorBrowsableState.Never)] - Mock InnerMock { get; } + Mock? InnerMock { get; } // /// // /// Gets the inner mocks of this setup (if present and known). @@ -126,7 +126,7 @@ public interface ISetup /// e.g. by or by . /// /// - Expression OriginalExpression { get; } + Expression? OriginalExpression { get; } /// /// Verifies this setup and optionally all verifiable setups of its inner mock (if present and known). diff --git a/src/Moq/InnerMockSetup.cs b/src/Moq/InnerMockSetup.cs index ef7ed6e17..3eaef8cac 100644 --- a/src/Moq/InnerMockSetup.cs +++ b/src/Moq/InnerMockSetup.cs @@ -11,9 +11,9 @@ namespace Moq { sealed class InnerMockSetup : SetupWithOutParameterSupport { - readonly object returnValue; + readonly object? returnValue; - public InnerMockSetup(Expression originalExpression, Mock mock, MethodExpectation expectation, object returnValue) + public InnerMockSetup(Expression? originalExpression, Mock mock, MethodExpectation expectation, object? returnValue) : base(originalExpression, mock, expectation) { Debug.Assert(Awaitable.TryGetResultRecursive(returnValue) is IMocked); diff --git a/src/Moq/Interception/CastleProxyFactory.cs b/src/Moq/Interception/CastleProxyFactory.cs index 5bca46b2d..c17d9be42 100644 --- a/src/Moq/Interception/CastleProxyFactory.cs +++ b/src/Moq/Interception/CastleProxyFactory.cs @@ -44,7 +44,7 @@ ProxyGenerator GetClassGenerator(Type mockType) } /// - public override object CreateProxy(Type mockType, Moq.IInterceptor interceptor, Type[] interfaces, object[] arguments) + public override object CreateProxy(Type mockType, Moq.IInterceptor interceptor, Type[] interfaces, object?[] arguments) { // All generated proxies need to implement `IProxy`: var additionalInterfaces = new Type[1 + interfaces.Length]; @@ -62,7 +62,7 @@ public override object CreateProxy(Type mockType, Moq.IInterceptor interceptor, var options = new ProxyGenerationOptions(); options.AddDelegateTypeMixin(mockType); var container = GetClassGenerator(mockType).CreateClassProxy(typeof(object), additionalInterfaces, options, new Interceptor(interceptor)); - return Delegate.CreateDelegate(mockType, container, container.GetType().GetMethod("Invoke")); + return Delegate.CreateDelegate(mockType, container, container.GetType().GetMethod("Invoke")!); } try @@ -91,7 +91,7 @@ public override bool IsTypeVisible(Type type) sealed class Interceptor : Castle.DynamicProxy.IInterceptor { - static readonly MethodInfo proxyInterceptorGetter = typeof(IProxy).GetProperty(nameof(IProxy.Interceptor)).GetMethod; + static readonly MethodInfo proxyInterceptorGetter = typeof(IProxy).GetProperty(nameof(IProxy.Interceptor))!.GetMethod!; Moq.IInterceptor interceptor; @@ -129,7 +129,7 @@ public void Intercept(Castle.DynamicProxy.IInvocation underlying) sealed class Invocation : Moq.Invocation { - Castle.DynamicProxy.IInvocation underlying; + Castle.DynamicProxy.IInvocation? underlying; internal Invocation(Castle.DynamicProxy.IInvocation underlying) : base(underlying.Proxy.GetType(), underlying.Method, underlying.Arguments) { @@ -142,7 +142,7 @@ protected internal override object CallBase() #if FEATURE_DEFAULT_INTERFACE_IMPLEMENTATIONS var method = this.Method; - if (method.DeclaringType.IsInterface && !method.IsAbstract) + if (method.DeclaringType!.IsInterface && !method.IsAbstract) { // As of version 4.4.0, DynamicProxy cannot proceed to default method implementations of interfaces. // we need to find and call those manually. @@ -165,12 +165,12 @@ public void DetachFromUnderlying() // Finding and calling default interface implementations currently involves a lot of reflection, // we are using two caches to speed up these operations for repeated calls. static ConcurrentDictionary, MethodInfo> mostSpecificOverrides; - static ConcurrentDictionary> nonVirtualInvocationThunks; + static ConcurrentDictionary> nonVirtualInvocationThunks; static CastleProxyFactory() { mostSpecificOverrides = new ConcurrentDictionary, MethodInfo>(); - nonVirtualInvocationThunks = new ConcurrentDictionary>(); + nonVirtualInvocationThunks = new ConcurrentDictionary>(); } /// @@ -189,7 +189,7 @@ public static MethodInfo FindMostSpecificOverride(MethodInfo declaration, Type p var genericParameterCount = declaration.IsGenericMethod ? declaration.GetGenericArguments().Length : 0; var returnType = declaration.ReturnType; var parameterTypes = declaration.GetParameterTypes().ToArray(); - var declaringType = declaration.DeclaringType; + var declaringType = declaration.DeclaringType!; // If the base class has a method implementation, then by rule (2) it will be more specific // than any candidate method from an implemented interface: @@ -232,7 +232,7 @@ public static MethodInfo FindMostSpecificOverride(MethodInfo declaration, Type p // No, it is the most specific override so far. Add it to the list, but before doing so, // remove all less specific overrides from it: - candidateMethods.ExceptWith(candidateMethods.Where(cm => cm.DeclaringType.IsAssignableFrom(implementedInterface)).ToArray()); + candidateMethods.ExceptWith(candidateMethods.Where(cm => cm.DeclaringType!.IsAssignableFrom(implementedInterface)).ToArray()); candidateMethods.Add(candidateMethod); } @@ -256,7 +256,7 @@ public static MethodInfo FindMostSpecificOverride(MethodInfo declaration, Type p /// Performs a non-virtual (non-polymorphic) call to the given /// using the specified object and . /// - public static object DynamicInvokeNonVirtually(MethodInfo method, object instance, object[] arguments) + public static object DynamicInvokeNonVirtually(MethodInfo method, object instance, object?[] arguments) { // There are a couple of probable alternatives to the following implementation that // unfortunately don't work in practice: @@ -293,7 +293,7 @@ public static object DynamicInvokeNonVirtually(MethodInfo method, object instanc { if (parameterTypes[i].IsByRef) { - parameterTypes[i] = parameterTypes[i].GetElementType(); + parameterTypes[i] = parameterTypes[i].GetElementType()!; } } @@ -320,7 +320,7 @@ public static object DynamicInvokeNonVirtually(MethodInfo method, object instanc // Perform the actual call. il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Castclass, method.DeclaringType); + il.Emit(OpCodes.Castclass, method.DeclaringType!); for (var i = 0; i < n; ++i) { il.Emit(originalParameterTypes[i].IsByRef ? OpCodes.Ldloca : OpCodes.Ldloc, arguments[i]); @@ -355,7 +355,7 @@ public static object DynamicInvokeNonVirtually(MethodInfo method, object instanc il.Emit(OpCodes.Ldloc, returnValue); il.Emit(OpCodes.Ret); - return (Func)dynamicMethod.CreateDelegate(typeof(Func)); + return (Func)dynamicMethod.CreateDelegate(typeof(Func)); }); return thunk.Invoke(instance, arguments); diff --git a/src/Moq/Interception/InterceptionAspects.cs b/src/Moq/Interception/InterceptionAspects.cs index a3bf1b790..6b92f5e7c 100644 --- a/src/Moq/Interception/InterceptionAspects.cs +++ b/src/Moq/Interception/InterceptionAspects.cs @@ -117,7 +117,7 @@ public static bool Handle(Invocation invocation, Mock mock) if (methodName[0] == 'a' && methodName[3] == '_' && invocation.Method.IsEventAddAccessor()) { var implementingMethod = invocation.Method.GetImplementingMethod(invocation.ProxyType); - var @event = implementingMethod.DeclaringType.GetEvents(bindingFlags).SingleOrDefault(e => e.GetAddMethod(true) == implementingMethod); + var @event = implementingMethod.DeclaringType!.GetEvents(bindingFlags).SingleOrDefault(e => e.GetAddMethod(true) == implementingMethod); if (@event != null) { if (mock.CallBase && !invocation.Method.IsAbstract) @@ -135,7 +135,7 @@ public static bool Handle(Invocation invocation, Mock mock) else if (methodName[0] == 'r' && methodName.Length > 7 && methodName[6] == '_' && invocation.Method.IsEventRemoveAccessor()) { var implementingMethod = invocation.Method.GetImplementingMethod(invocation.ProxyType); - var @event = implementingMethod.DeclaringType.GetEvents(bindingFlags).SingleOrDefault(e => e.GetRemoveMethod(true) == implementingMethod); + var @event = implementingMethod.DeclaringType!.GetEvents(bindingFlags).SingleOrDefault(e => e.GetRemoveMethod(true) == implementingMethod); if (@event != null) { if (mock.CallBase && !invocation.Method.IsAbstract) diff --git a/src/Moq/Interception/InterfaceProxy.cs b/src/Moq/Interception/InterfaceProxy.cs index 2b3a272c9..3d8b91347 100644 --- a/src/Moq/Interception/InterfaceProxy.cs +++ b/src/Moq/Interception/InterfaceProxy.cs @@ -17,19 +17,19 @@ namespace Moq.Internals [EditorBrowsable(EditorBrowsableState.Never)] public abstract class InterfaceProxy { - static MethodInfo equalsMethod = typeof(object).GetMethod("Equals", BindingFlags.Public | BindingFlags.Instance); - static MethodInfo getHashCodeMethod = typeof(object).GetMethod("GetHashCode", BindingFlags.Public | BindingFlags.Instance); - static MethodInfo toStringMethod = typeof(object).GetMethod("ToString", BindingFlags.Public | BindingFlags.Instance); + static MethodInfo equalsMethod = typeof(object).GetMethod("Equals", BindingFlags.Public | BindingFlags.Instance)!; + static MethodInfo getHashCodeMethod = typeof(object).GetMethod("GetHashCode", BindingFlags.Public | BindingFlags.Instance)!; + static MethodInfo toStringMethod = typeof(object).GetMethod("ToString", BindingFlags.Public | BindingFlags.Instance)!; /// [DebuggerHidden] - public sealed override bool Equals(object obj) + public sealed override bool Equals(object? obj) { // Forward this call to the interceptor, so that `object.Equals` can be set up. var interceptor = (IInterceptor)((IProxy)this).Interceptor; var invocation = new Invocation(this.GetType(), equalsMethod, obj); interceptor.Intercept(invocation); - return (bool)invocation.ReturnValue; + return (bool)invocation.ReturnValue!; } /// @@ -40,25 +40,25 @@ public sealed override int GetHashCode() var interceptor = (IInterceptor)((IProxy)this).Interceptor; var invocation = new Invocation(this.GetType(), getHashCodeMethod); interceptor.Intercept(invocation); - return (int)invocation.ReturnValue; + return (int)invocation.ReturnValue!; } /// [DebuggerHidden] - public sealed override string ToString() + public sealed override string? ToString() { // Forward this call to the interceptor, so that `object.ToString` can be set up. var interceptor = (IInterceptor)((IProxy)this).Interceptor; var invocation = new Invocation(this.GetType(), toStringMethod); interceptor.Intercept(invocation); - return (string)invocation.ReturnValue; + return (string?)invocation.ReturnValue; } sealed class Invocation : Moq.Invocation { - static object[] noArguments = new object[0]; + static object?[] noArguments = new object?[0]; - public Invocation(Type proxyType, MethodInfo method, params object[] arguments) + public Invocation(Type proxyType, MethodInfo method, params object?[] arguments) : base(proxyType, method, arguments) { } diff --git a/src/Moq/Interception/ProxyFactory.cs b/src/Moq/Interception/ProxyFactory.cs index 6ad56f18c..ca35694b9 100644 --- a/src/Moq/Interception/ProxyFactory.cs +++ b/src/Moq/Interception/ProxyFactory.cs @@ -13,7 +13,7 @@ abstract class ProxyFactory /// public static ProxyFactory Instance { get; } = new CastleProxyFactory(); - public abstract object CreateProxy(Type mockType, IInterceptor interceptor, Type[] interfaces, object[] arguments); + public abstract object CreateProxy(Type mockType, IInterceptor interceptor, Type[] interfaces, object?[] arguments); public abstract bool IsMethodVisible(MethodInfo method, out string messageIfNotVisible); diff --git a/src/Moq/Invocation.cs b/src/Moq/Invocation.cs index 9b8a237f2..19a918c7c 100644 --- a/src/Moq/Invocation.cs +++ b/src/Moq/Invocation.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Text; @@ -13,12 +14,12 @@ namespace Moq { abstract class Invocation : IInvocation { - object[] arguments; + object?[] arguments; MethodInfo method; - MethodInfo methodImplementation; + MethodInfo? methodImplementation; readonly Type proxyType; - object result; - Setup matchingSetup; + object? result; + Setup? matchingSetup; bool verified; /// @@ -27,7 +28,7 @@ abstract class Invocation : IInvocation /// The of the concrete proxy object on which a method is being invoked. /// The method being invoked. /// The arguments with which the specified is being invoked. - protected Invocation(Type proxyType, MethodInfo method, params object[] arguments) + protected Invocation(Type proxyType, MethodInfo method, params object?[] arguments) { Debug.Assert(proxyType != null); Debug.Assert(arguments != null); @@ -63,15 +64,15 @@ public MethodInfo MethodImplementation /// Arguments may be modified. Derived classes must ensure that by-reference parameters are written back /// when the invocation is ended by a call to any of the three Returns methods. /// - public object[] Arguments => this.arguments; + public object?[] Arguments => this.arguments; - IReadOnlyList IInvocation.Arguments => this.arguments; + IReadOnlyList IInvocation.Arguments => this.arguments; - public ISetup MatchingSetup => this.matchingSetup; + public ISetup? MatchingSetup => this.matchingSetup; public Type ProxyType => this.proxyType; - public object ReturnValue + public object? ReturnValue { get => this.result is ExceptionResult ? null : this.result; set @@ -80,8 +81,10 @@ public object ReturnValue this.result = value; } } - - public Exception Exception +#if NULLABLE_REFERENCE_TYPES + [DisallowNull] +#endif + public Exception? Exception { get => this.result is ExceptionResult r ? r.Exception : null; set @@ -134,7 +137,7 @@ public override string ToString() var method = this.Method; var builder = new StringBuilder(); - builder.AppendNameOf(method.DeclaringType); + builder.AppendNameOf(method.DeclaringType!); builder.Append('.'); if (method.IsGetAccessor()) diff --git a/src/Moq/InvocationCollection.cs b/src/Moq/InvocationCollection.cs index decfcd4cc..2783f2cb2 100644 --- a/src/Moq/InvocationCollection.cs +++ b/src/Moq/InvocationCollection.cs @@ -10,7 +10,7 @@ namespace Moq { sealed class InvocationCollection : IInvocationList { - Invocation[] invocations; + Invocation[] invocations = new Invocation[0]; int capacity = 0; int count = 0; @@ -72,7 +72,7 @@ public void Clear() lock (this.invocationsLock) { // Replace the collection so readers with a reference to the old collection aren't interrupted - this.invocations = null; + this.invocations = new Invocation[0]; this.count = 0; this.capacity = 0; diff --git a/src/Moq/It.cs b/src/Moq/It.cs index d6c81f237..1bfc9d996 100644 --- a/src/Moq/It.cs +++ b/src/Moq/It.cs @@ -32,7 +32,7 @@ public static class Ref /// /// Matches any value that is assignment-compatible with type . /// - public static TValue IsAny; + public static TValue IsAny = default(TValue)!; } /// @@ -64,7 +64,7 @@ public static TValue IsAny() } } - static readonly MethodInfo isAnyMethod = typeof(It).GetMethod(nameof(It.IsAny), BindingFlags.Public | BindingFlags.Static); + static readonly MethodInfo isAnyMethod = typeof(It).GetMethod(nameof(It.IsAny), BindingFlags.Public | BindingFlags.Static)!; internal static MethodCallExpression IsAny(Type genericArgument) { @@ -143,7 +143,7 @@ public static TValue Is(Expression> match) throw new ArgumentException(Resources.UseItIsOtherOverload, nameof(match)); } - var thisMethod = (MethodInfo)MethodBase.GetCurrentMethod(); + var thisMethod = (MethodInfo)MethodBase.GetCurrentMethod()!; var compiledMatchMethod = match.CompileUsingExpressionCompiler(); return Match.Create( @@ -165,9 +165,9 @@ public static TValue Is(Expression> match) /// Allows the specification of a predicate to perform matching of method call arguments. /// [EditorBrowsable(EditorBrowsableState.Advanced)] - public static TValue Is(Expression> match) + public static TValue Is(Expression> match) { - var thisMethod = (MethodInfo)MethodBase.GetCurrentMethod(); + var thisMethod = (MethodInfo)MethodBase.GetCurrentMethod()!; var compiledMatchMethod = match.CompileUsingExpressionCompiler(); return Match.Create( diff --git a/src/Moq/Language/Flow/SetupPhrase.cs b/src/Moq/Language/Flow/SetupPhrase.cs index e6409b9a6..dcbdff386 100644 --- a/src/Moq/Language/Flow/SetupPhrase.cs +++ b/src/Moq/Language/Flow/SetupPhrase.cs @@ -286,9 +286,9 @@ public void Verifiable(string failMessage) public void Verifiable(Times times) => this.Verifiable(times, null); - public void Verifiable(Func times, string failMessage) => this.Verifiable(times(), failMessage); + public void Verifiable(Func times, string? failMessage) => this.Verifiable(times(), failMessage); - public void Verifiable(Times times, string failMessage) + public void Verifiable(Times times, string? failMessage) { this.setup.MarkAsVerifiable(); this.setup.SetExpectedInvocationCount(times); diff --git a/src/Moq/Linq/MockSetupsBuilder.cs b/src/Moq/Linq/MockSetupsBuilder.cs index 77a946eeb..c73583486 100644 --- a/src/Moq/Linq/MockSetupsBuilder.cs +++ b/src/Moq/Linq/MockSetupsBuilder.cs @@ -103,7 +103,7 @@ protected override Expression VisitUnary(UnaryExpression node) return base.VisitUnary(node); } - static Expression ConvertToSetup(Expression left, Expression right) + static Expression? ConvertToSetup(Expression left, Expression right) { switch (left.NodeType) { diff --git a/src/Moq/LookupOrFallbackDefaultValueProvider.cs b/src/Moq/LookupOrFallbackDefaultValueProvider.cs index a5ea28548..173617047 100644 --- a/src/Moq/LookupOrFallbackDefaultValueProvider.cs +++ b/src/Moq/LookupOrFallbackDefaultValueProvider.cs @@ -34,14 +34,14 @@ namespace Moq [EditorBrowsable(EditorBrowsableState.Advanced)] public abstract class LookupOrFallbackDefaultValueProvider : DefaultValueProvider { - Dictionary> factories; + Dictionary?> factories; /// /// Initializes a new instance of the class. /// protected LookupOrFallbackDefaultValueProvider() { - this.factories = new Dictionary>() + this.factories = new Dictionary?>() { ["System.ValueTuple`1"] = CreateValueTupleOf, ["System.ValueTuple`2"] = CreateValueTupleOf, @@ -61,7 +61,7 @@ protected LookupOrFallbackDefaultValueProvider() /// The type(s) for which to remove any registered factory function. protected void Deregister(Type factoryKey) { - Debug.Assert(factoryKey != null); + Debug.Assert(factoryKey.FullName != null); // NOTE: In order to be able to unregister the default logic for awaitable types, // we need a way (below) to know when to delegate to an `IAwaitableFactory`, and when not to. @@ -93,7 +93,7 @@ protected void Register(Type factoryKey, Func factory) } /// - protected internal sealed override object GetDefaultParameterValue(ParameterInfo parameter, Mock mock) + protected internal sealed override object? GetDefaultParameterValue(ParameterInfo parameter, Mock mock) { Debug.Assert(parameter != null); Debug.Assert(parameter.ParameterType != typeof(void)); @@ -103,7 +103,7 @@ protected internal sealed override object GetDefaultParameterValue(ParameterInfo } /// - protected internal sealed override object GetDefaultReturnValue(MethodInfo method, Mock mock) + protected internal sealed override object? GetDefaultReturnValue(MethodInfo method, Mock mock) { Debug.Assert(method != null); Debug.Assert(method.ReturnType != typeof(void)); @@ -113,7 +113,7 @@ protected internal sealed override object GetDefaultReturnValue(MethodInfo metho } /// - protected internal sealed override object GetDefaultValue(Type type, Mock mock) + protected internal sealed override object? GetDefaultValue(Type type, Mock mock) { Debug.Assert(type != null); Debug.Assert(type != typeof(void)); @@ -123,8 +123,8 @@ protected internal sealed override object GetDefaultValue(Type type, Mock mock) : type.IsArray ? typeof(Array) : type; - Func factory; - if (this.factories.TryGetValue(handlerKey, out factory) || this.factories.TryGetValue(handlerKey.FullName, out factory)) + Func? factory; + if (this.factories.TryGetValue(handlerKey, out factory) || this.factories.TryGetValue(handlerKey.FullName!, out factory)) { if (factory != null) // This prevents delegation to an `IAwaitableFactory` for deregistered awaitable types; see note above. { @@ -147,7 +147,7 @@ protected internal sealed override object GetDefaultValue(Type type, Mock mock) /// /// The type of which to produce a value. /// The on which an unexpected invocation has occurred. - protected virtual object GetFallbackDefaultValue(Type type, Mock mock) + protected virtual object? GetFallbackDefaultValue(Type type, Mock mock) { Debug.Assert(type != null); Debug.Assert(type != typeof(void)); @@ -159,12 +159,12 @@ protected virtual object GetFallbackDefaultValue(Type type, Mock mock) object CreateValueTupleOf(Type type, Mock mock) { var itemTypes = type.GetGenericArguments(); - var items = new object[itemTypes.Length]; + var items = new object?[itemTypes.Length]; for (int i = 0, n = itemTypes.Length; i < n; ++i) { items[i] = this.GetDefaultValue(itemTypes[i], mock); } - return Activator.CreateInstance(type, items); + return Activator.CreateInstance(type, items)!; } } } diff --git a/src/Moq/Match.cs b/src/Moq/Match.cs index 00707390f..bcb089287 100644 --- a/src/Moq/Match.cs +++ b/src/Moq/Match.cs @@ -68,17 +68,18 @@ public abstract class Match : IMatcher /// internal static TValue Matcher() { - return default(TValue); + return default(TValue)!; } - internal abstract bool Matches(object argument, Type parameterType); + internal abstract bool Matches(object? argument, Type parameterType); - internal abstract void SetupEvaluatedSuccessfully(object argument, Type parameterType); + internal abstract void SetupEvaluatedSuccessfully(object? argument, Type parameterType); - bool IMatcher.Matches(object argument, Type parameterType) => this.Matches(argument, parameterType); + bool IMatcher.Matches(object? argument, Type parameterType) => this.Matches(argument, parameterType); - void IMatcher.SetupEvaluatedSuccessfully(object value, Type parameterType) => this.SetupEvaluatedSuccessfully(value, parameterType); + void IMatcher.SetupEvaluatedSuccessfully(object? value, Type parameterType) => this.SetupEvaluatedSuccessfully(value, parameterType); + // TODO: Consider making the constructor set this to avoid the nullable reference warning. internal Expression RenderExpression { get; set; } /// @@ -89,7 +90,7 @@ internal static TValue Matcher() public static T Create(Predicate condition) { Match.Register(new Match(condition, () => Matcher())); - return default(T); + return default(T)!; } /// @@ -104,7 +105,7 @@ public static T Create(Predicate condition) public static T Create(Predicate condition, Expression> renderExpression) { Match.Register(new Match(condition, renderExpression)); - return default(T); + return default(T)!; } /// @@ -125,13 +126,13 @@ public static T Create(Predicate condition, Expression> renderExpr /// /// A lambda representation of the matcher. /// - public static T Create(Func condition, Expression> renderExpression) + public static T Create(Func condition, Expression> renderExpression) { Guard.NotNull(condition, nameof(condition)); Guard.NotNull(renderExpression, nameof(renderExpression)); Match.Register(new MatchFactory(condition, renderExpression)); - return default(T); + return default(T)!; } internal static void Register(Match match) @@ -163,29 +164,29 @@ internal static void Register(Match match) public class Match : Match, IEquatable> { internal Predicate Condition { get; set; } - internal Action Success { get; set; } + internal Action? Success { get; set; } - internal Match(Predicate condition, Expression> renderExpression, Action success = null) + internal Match(Predicate condition, Expression> renderExpression, Action? success = null) { this.Condition = condition; this.RenderExpression = renderExpression.Body.Apply(EvaluateCaptures.Rewriter); this.Success = success; } - internal override bool Matches(object argument, Type parameterType) + internal override bool Matches(object? argument, Type parameterType) { - return CanCast(argument) && this.Condition((T)argument); + return CanCast(argument) && this.Condition((T)argument!); } - internal override void SetupEvaluatedSuccessfully(object argument, Type parameterType) + internal override void SetupEvaluatedSuccessfully(object? argument, Type parameterType) { Debug.Assert(this.Matches(argument, parameterType)); Debug.Assert(CanCast(argument)); - this.Success?.Invoke((T)argument); + this.Success?.Invoke((T)argument!); } - static bool CanCast(object value) + static bool CanCast(object? value) { if (value != null) { @@ -199,15 +200,19 @@ static bool CanCast(object value) } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is Match other && this.Equals(other); } /// - public bool Equals(Match other) + public bool Equals(Match? other) { - if (this.Condition == other.Condition) + if (other == null) + { + return false; + } + else if (this.Condition == other.Condition) { return true; } @@ -234,9 +239,9 @@ public bool Equals(Match other) sealed class MatchFactory : Match { - readonly Func condition; + readonly Func condition; - internal MatchFactory(Func condition, LambdaExpression renderExpression) + internal MatchFactory(Func condition, LambdaExpression renderExpression) { Debug.Assert(condition != null); Debug.Assert(renderExpression != null); @@ -245,18 +250,18 @@ internal MatchFactory(Func condition, LambdaExpression rende this.RenderExpression = renderExpression.Body.Apply(EvaluateCaptures.Rewriter); } - internal override bool Matches(object argument, Type parameterType) + internal override bool Matches(object? argument, Type parameterType) { - var canCast = (Predicate)Delegate.CreateDelegate(typeof(Predicate), canCastMethod.MakeGenericMethod(parameterType)); + var canCast = (Predicate)Delegate.CreateDelegate(typeof(Predicate), canCastMethod.MakeGenericMethod(parameterType)); return canCast(argument) && condition(argument, parameterType); } - internal override void SetupEvaluatedSuccessfully(object argument, Type parameterType) + internal override void SetupEvaluatedSuccessfully(object? argument, Type parameterType) { Debug.Assert(this.Matches(argument, parameterType)); } - static bool CanCast(object value) + static bool CanCast(object? value) { if (value != null) { @@ -269,7 +274,7 @@ static bool CanCast(object value) } } - static readonly MethodInfo canCastMethod = typeof(MatchFactory).GetMethod("CanCast", BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly); + static readonly MethodInfo canCastMethod = typeof(MatchFactory).GetMethod(nameof(CanCast), BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly)!; // TODO: Check whether we need to implement `IEquatable<>` to make this work with delegate-based // setup & verification methods such as `SetupSet`! diff --git a/src/Moq/MatcherFactory.cs b/src/Moq/MatcherFactory.cs index 6c6484615..b11476092 100644 --- a/src/Moq/MatcherFactory.cs +++ b/src/Moq/MatcherFactory.cs @@ -52,7 +52,7 @@ public static Pair CreateMatcher(Expression argument, Para var member = memberExpression.Member; if (member.Name == nameof(It.Ref.IsAny)) { - var memberDeclaringType = member.DeclaringType; + var memberDeclaringType = member.DeclaringType!; if (memberDeclaringType.IsGenericType) { var memberDeclaringTypeDefinition = memberDeclaringType.GetGenericTypeDefinition(); @@ -77,7 +77,7 @@ public static Pair CreateMatcher(Expression argument, Para var newArrayExpression = (NewArrayExpression)argument; Debug.Assert(newArrayExpression.Type.IsArray); - var elementType = newArrayExpression.Type.GetElementType(); + var elementType = newArrayExpression.Type.GetElementType()!; var n = newArrayExpression.Expressions.Count; var matchers = new IMatcher[n]; diff --git a/src/Moq/MatcherObserver.cs b/src/Moq/MatcherObserver.cs index 41c8e32f1..e30c3692d 100644 --- a/src/Moq/MatcherObserver.cs +++ b/src/Moq/MatcherObserver.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; namespace Moq @@ -21,7 +22,7 @@ namespace Moq sealed class MatcherObserver : IDisposable { [ThreadStatic] - static Stack activations; + static Stack? activations; public static MatcherObserver Activate() { @@ -37,7 +38,11 @@ public static MatcherObserver Activate() return activation; } - public static bool IsActive(out MatcherObserver observer) +#if NULLABLE_REFERENCE_TYPES + public static bool IsActive([NotNullWhen(true)] out MatcherObserver? observer) +#else + public static bool IsActive(out MatcherObserver? observer) +#endif { var activations = MatcherObserver.activations; @@ -54,7 +59,7 @@ public static bool IsActive(out MatcherObserver observer) } int timestamp; - List observations; + List? observations; MatcherObserver() { @@ -94,7 +99,11 @@ public void OnMatch(Match match) /// and if so, returns the last one. /// /// The observed matcher observed last. - public bool TryGetLastMatch(out Match match) +#if NULLABLE_REFERENCE_TYPES + public bool TryGetLastMatch([NotNullWhen(true)] out Match? match) +#else + public bool TryGetLastMatch(out Match? match) +#endif { if (this.observations != null && this.observations.Count > 0) { diff --git a/src/Moq/Matchers/AnyMatcher.cs b/src/Moq/Matchers/AnyMatcher.cs index 11135338c..e7680e4e8 100644 --- a/src/Moq/Matchers/AnyMatcher.cs +++ b/src/Moq/Matchers/AnyMatcher.cs @@ -14,9 +14,9 @@ sealed class AnyMatcher : IMatcher { } - public bool Matches(object argument, Type parameterType) => true; + public bool Matches(object? argument, Type parameterType) => true; - public void SetupEvaluatedSuccessfully(object argument, Type parameterType) + public void SetupEvaluatedSuccessfully(object? argument, Type parameterType) { Debug.Assert(this.Matches(argument, parameterType)); } diff --git a/src/Moq/Matchers/ConstantMatcher.cs b/src/Moq/Matchers/ConstantMatcher.cs index f43a2e84e..eb8aa06ba 100644 --- a/src/Moq/Matchers/ConstantMatcher.cs +++ b/src/Moq/Matchers/ConstantMatcher.cs @@ -10,14 +10,14 @@ namespace Moq.Matchers { class ConstantMatcher : IMatcher { - object constantValue; + object? constantValue; - public ConstantMatcher(object constantValue) + public ConstantMatcher(object? constantValue) { this.constantValue = constantValue; } - public bool Matches(object argument, Type parameterType) + public bool Matches(object? argument, Type parameterType) { if (object.Equals(argument, constantValue)) { @@ -36,14 +36,14 @@ public bool Matches(object argument, Type parameterType) return false; } - public void SetupEvaluatedSuccessfully(object argument, Type parameterType) + public void SetupEvaluatedSuccessfully(object? argument, Type parameterType) { Debug.Assert(this.Matches(argument, parameterType)); } bool MatchesEnumerable(IEnumerable enumerable) { - var constValues = (IEnumerable)constantValue; + var constValues = (IEnumerable)constantValue!; return constValues.Cast().SequenceEqual(enumerable.Cast()); } } diff --git a/src/Moq/Matchers/ExpressionMatcher.cs b/src/Moq/Matchers/ExpressionMatcher.cs index 72b910a82..7cd11e52e 100644 --- a/src/Moq/Matchers/ExpressionMatcher.cs +++ b/src/Moq/Matchers/ExpressionMatcher.cs @@ -16,13 +16,13 @@ public ExpressionMatcher(Expression expression) this.expression = expression; } - public bool Matches(object argument, Type parameterType) + public bool Matches(object? argument, Type parameterType) { return argument is Expression valueExpression && ExpressionComparer.Default.Equals(this.expression, valueExpression); } - public void SetupEvaluatedSuccessfully(object argument, Type parameterType) + public void SetupEvaluatedSuccessfully(object? argument, Type parameterType) { Debug.Assert(this.Matches(argument, parameterType)); } diff --git a/src/Moq/Matchers/LazyEvalMatcher.cs b/src/Moq/Matchers/LazyEvalMatcher.cs index 3f5fc8cfc..f8d2e2948 100644 --- a/src/Moq/Matchers/LazyEvalMatcher.cs +++ b/src/Moq/Matchers/LazyEvalMatcher.cs @@ -16,13 +16,13 @@ public LazyEvalMatcher(Expression expression) this.expression = expression; } - public bool Matches(object argument, Type parameterType) + public bool Matches(object? argument, Type parameterType) { var eval = Evaluator.PartialEval(this.expression); return eval is ConstantExpression ce && new ConstantMatcher(ce.Value).Matches(argument, parameterType); } - public void SetupEvaluatedSuccessfully(object argument, Type parameterType) + public void SetupEvaluatedSuccessfully(object? argument, Type parameterType) { Debug.Assert(this.Matches(argument, parameterType)); } diff --git a/src/Moq/Matchers/MatcherAttributeMatcher.cs b/src/Moq/Matchers/MatcherAttributeMatcher.cs index 1ce03f04c..1cd0f7bc7 100644 --- a/src/Moq/Matchers/MatcherAttributeMatcher.cs +++ b/src/Moq/Matchers/MatcherAttributeMatcher.cs @@ -43,7 +43,7 @@ static MethodInfo ResolveValidatorMethod(MethodCallExpression call) { var expectedParametersTypes = new[] { call.Method.ReturnType }.Concat(call.Method.GetParameters().Select(p => p.ParameterType)).ToArray(); - MethodInfo method = null; + MethodInfo? method; if (call.Method.IsGenericMethod) { @@ -51,7 +51,7 @@ static MethodInfo ResolveValidatorMethod(MethodCallExpression call) // passing generic type arguments for the query. var genericArgs = call.Method.GetGenericArguments(); - method = call.Method.DeclaringType.GetMethods(call.Method.Name) + method = call.Method.DeclaringType!.GetMethods(call.Method.Name) .Where(m => m.IsGenericMethodDefinition && m.GetGenericArguments().Length == @@ -63,7 +63,7 @@ static MethodInfo ResolveValidatorMethod(MethodCallExpression call) } else { - method = call.Method.DeclaringType.GetMethod(call.Method.Name, expectedParametersTypes); + method = call.Method.DeclaringType!.GetMethod(call.Method.Name, expectedParametersTypes); } // throw if validatorMethod doesn't exists @@ -74,22 +74,22 @@ static MethodInfo ResolveValidatorMethod(MethodCallExpression call) call.Method.IsStatic ? "static " : String.Empty, call.Method.Name, String.Join(", ", expectedParametersTypes.Select(x => x.Name).ToArray()), - call.Method.DeclaringType.ToString())); + call.Method.DeclaringType!.ToString())); } return method; } - public bool Matches(object argument, Type parameterType) + public bool Matches(object? argument, Type parameterType) { // use matcher Expression to get extra arguments var extraArgs = this.expression.Arguments.Select(ae => ((ConstantExpression)ae.PartialEval()).Value); var args = new[] { argument }.Concat(extraArgs).ToArray(); // for static and non-static method - var instance = this.expression.Object == null ? null : (this.expression.Object.PartialEval() as ConstantExpression).Value; - return (bool)validatorMethod.Invoke(instance, args); + var instance = this.expression.Object == null ? null : ((ConstantExpression)this.expression.Object.PartialEval()).Value; + return (bool)validatorMethod.Invoke(instance, args)!; } - public void SetupEvaluatedSuccessfully(object argument, Type parameterType) + public void SetupEvaluatedSuccessfully(object? argument, Type parameterType) { Debug.Assert(this.Matches(argument, parameterType)); } diff --git a/src/Moq/Matchers/ParamArrayMatcher.cs b/src/Moq/Matchers/ParamArrayMatcher.cs index 4f1a2f46e..a65b2ee75 100644 --- a/src/Moq/Matchers/ParamArrayMatcher.cs +++ b/src/Moq/Matchers/ParamArrayMatcher.cs @@ -17,14 +17,14 @@ public ParamArrayMatcher(IMatcher[] matchers) this.matchers = matchers; } - public bool Matches(object argument, Type parameterType) + public bool Matches(object? argument, Type parameterType) { if (argument is not Array values || this.matchers.Length != values.Length) { return false; } - var elementType = parameterType.GetElementType(); + var elementType = parameterType.GetElementType()!; for (int index = 0; index < values.Length; index++) { @@ -37,13 +37,13 @@ public bool Matches(object argument, Type parameterType) return true; } - public void SetupEvaluatedSuccessfully(object argument, Type parameterType) + public void SetupEvaluatedSuccessfully(object? argument, Type parameterType) { Debug.Assert(this.Matches(argument, parameterType)); Debug.Assert(argument is Array array && array.Length == this.matchers.Length); var values = (Array)argument; - var elementType = parameterType.GetElementType(); + var elementType = parameterType.GetElementType()!; for (int i = 0, n = this.matchers.Length; i < n; ++i) { this.matchers[i].SetupEvaluatedSuccessfully(values.GetValue(i), elementType); diff --git a/src/Moq/Matchers/RefMatcher.cs b/src/Moq/Matchers/RefMatcher.cs index 1d3efec67..effb206ef 100644 --- a/src/Moq/Matchers/RefMatcher.cs +++ b/src/Moq/Matchers/RefMatcher.cs @@ -8,22 +8,22 @@ namespace Moq.Matchers { class RefMatcher : IMatcher { - readonly object reference; + readonly object? reference; readonly bool referenceIsValueType; - public RefMatcher(object reference) + public RefMatcher(object? reference) { this.reference = reference; this.referenceIsValueType = reference?.GetType().IsValueType ?? false; } - public bool Matches(object argument, Type parameterType) + public bool Matches(object? argument, Type parameterType) { return this.referenceIsValueType ? object.Equals(this.reference, argument) : object.ReferenceEquals(this.reference, argument); } - public void SetupEvaluatedSuccessfully(object value, Type parameterType) + public void SetupEvaluatedSuccessfully(object? value, Type parameterType) { Debug.Assert(this.Matches(value, parameterType)); } diff --git a/src/Moq/MethodCall.cs b/src/Moq/MethodCall.cs index ffe4ce15d..2cf489fd8 100644 --- a/src/Moq/MethodCall.cs +++ b/src/Moq/MethodCall.cs @@ -20,16 +20,16 @@ namespace Moq { sealed partial class MethodCall : SetupWithOutParameterSupport { - VerifyInvocationCount verifyInvocationCount; - Behavior callback; - Behavior raiseEvent; - Behavior returnOrThrow; - Behavior afterReturnCallback; - Condition condition; - string failMessage; - string declarationSite; - - public MethodCall(Expression originalExpression, Mock mock, Condition condition, MethodExpectation expectation) + VerifyInvocationCount? verifyInvocationCount; + Behavior? callback; + Behavior? raiseEvent; + Behavior? returnOrThrow; + Behavior? afterReturnCallback; + Condition? condition; + string? failMessage; + string? declarationSite; + + public MethodCall(Expression originalExpression, Mock mock, Condition? condition, MethodExpectation expectation) : base(originalExpression, mock, expectation) { this.condition = condition; @@ -40,12 +40,12 @@ public MethodCall(Expression originalExpression, Mock mock, Condition condition, } } - public string FailMessage + public string? FailMessage { get => this.failMessage; } - public override Condition Condition => this.condition; + public override Condition? Condition => this.condition; public override IEnumerable InnerMocks { @@ -59,7 +59,7 @@ public override IEnumerable InnerMocks } } - static string GetUserCodeCallSite() + static string? GetUserCodeCallSite() { try { @@ -68,14 +68,14 @@ static string GetUserCodeCallSite() var frame = new StackTrace(true) .GetFrames() .SkipWhile(f => f.GetMethod() != thisMethod) - .SkipWhile(f => f.GetMethod().DeclaringType == null || f.GetMethod().DeclaringType.Assembly == mockAssembly) + .SkipWhile(f => f.GetMethod()!.DeclaringType == null || f.GetMethod()!.DeclaringType!.Assembly == mockAssembly) .FirstOrDefault(); var member = frame?.GetMethod(); if (member != null) { var declaredAt = new StringBuilder(); - declaredAt.AppendNameOf(member.DeclaringType).Append('.').AppendNameOf(member, false); - var fileName = Path.GetFileName(frame.GetFileName()); + declaredAt.AppendNameOf(member.DeclaringType!).Append('.').AppendNameOf(member, false); + var fileName = Path.GetFileName(frame!.GetFileName()); if (fileName != null) { declaredAt.Append(" in ").Append(fileName); @@ -144,7 +144,7 @@ public void SetCallbackBehavior(Delegate callback) throw new ArgumentNullException(nameof(callback)); } - ref Behavior behavior = ref (this.returnOrThrow == null) ? ref this.callback + ref Behavior? behavior = ref (this.returnOrThrow == null) ? ref this.callback : ref this.afterReturnCallback; if (callback is Action callbackWithoutArguments) @@ -224,7 +224,7 @@ public void SetReturnValueBehavior(object value) this.returnOrThrow = new ReturnValue(value); } - public void SetReturnComputedValueBehavior(Delegate valueFactory) + public void SetReturnComputedValueBehavior(Delegate? valueFactory) { Debug.Assert(this.Method.ReturnType != typeof(void)); Debug.Assert(this.returnOrThrow == null); @@ -302,7 +302,7 @@ public void SetThrowExceptionBehavior(Exception exception) this.returnOrThrow = new ThrowException(exception); } - public void SetThrowComputedExceptionBehavior(Delegate exceptionFactory) + public void SetThrowComputedExceptionBehavior(Delegate? exceptionFactory) { Debug.Assert(this.returnOrThrow == null); @@ -314,6 +314,7 @@ public void SetThrowComputedExceptionBehavior(Delegate exceptionFactory) // and instead of in `Throws(TException)`, we ended up in `Throws(Delegate)` or `Throws(Func)`, // which likely isn't what the user intended. // So here we do what we would've done in `Throws(TException)`: + // TODO: ThrowException expects non-null argument. this.returnOrThrow = new ThrowException(default); } else diff --git a/src/Moq/MethodExpectation.cs b/src/Moq/MethodExpectation.cs index 4112a4c10..9b3af6828 100644 --- a/src/Moq/MethodExpectation.cs +++ b/src/Moq/MethodExpectation.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -38,14 +39,14 @@ public static MethodExpectation CreateFrom(Invocation invocation) for (int i = 0; i < n; ++i) { var parameterType = parameterTypes[i]; - if (parameterType.IsByRef) parameterType = parameterType.GetElementType(); + if (parameterType.IsByRef) parameterType = parameterType.GetElementType()!; arguments[i] = E.Constant(invocation.Arguments[i], parameterType); } } LambdaExpression expression; { - var mock = E.Parameter(method.DeclaringType, "mock"); + var mock = E.Parameter(method.DeclaringType!, "mock"); expression = E.Lambda(E.Call(mock, method, arguments).Apply(UpgradePropertyAccessorMethods.Rewriter), mock); } @@ -68,16 +69,16 @@ public static MethodExpectation CreateFrom(Invocation invocation) public readonly IReadOnlyList Arguments; readonly IMatcher[] argumentMatchers; - IAwaitableFactory awaitableFactory; - MethodInfo methodImplementation; - Expression[] partiallyEvaluatedArguments; + IAwaitableFactory? awaitableFactory; + MethodInfo? methodImplementation; + Expression[]? partiallyEvaluatedArguments; #if DEBUG - Type proxyType; + Type? proxyType; #endif readonly bool exactGenericTypeArguments; - public MethodExpectation(LambdaExpression expression, MethodInfo method, IReadOnlyList arguments = null, bool exactGenericTypeArguments = false, bool skipMatcherInitialization = false, bool allowNonOverridable = false) + public MethodExpectation(LambdaExpression expression, MethodInfo method, IReadOnlyList? arguments = null, bool exactGenericTypeArguments = false, bool skipMatcherInitialization = false, bool allowNonOverridable = false) { Debug.Assert(expression != null); Debug.Assert(method != null); @@ -111,7 +112,11 @@ public void AddResultExpression(Func add, IAwaitableFactory awaitableFacto this.awaitableFactory = awaitableFactory; } - public override bool HasResultExpression(out IAwaitableFactory awaitableFactory) +#if NULLABLE_REFERENCE_TYPES + public override bool HasResultExpression([NotNullWhen(true)] out IAwaitableFactory? awaitableFactory) +#else + public override bool HasResultExpression(out IAwaitableFactory? awaitableFactory) +#endif { return (awaitableFactory = this.awaitableFactory) != null; } @@ -199,7 +204,7 @@ bool IsOverride(Invocation invocation) return true; } - public override bool Equals(Expectation obj) + public override bool Equals(Expectation? obj) { if (obj is not MethodExpectation other) return false; diff --git a/src/Moq/MethodSetup.cs b/src/Moq/MethodSetup.cs index c6bb2b4fb..19fdee19c 100644 --- a/src/Moq/MethodSetup.cs +++ b/src/Moq/MethodSetup.cs @@ -11,7 +11,7 @@ namespace Moq /// abstract class MethodSetup : Setup { - protected MethodSetup(Expression originalExpression, Mock mock, MethodExpectation expectation) + protected MethodSetup(Expression? originalExpression, Mock mock, MethodExpectation expectation) : base(originalExpression, mock, expectation) { } diff --git a/src/Moq/Mock.cs b/src/Moq/Mock.cs index 958494f17..b178f0475 100644 --- a/src/Moq/Mock.cs +++ b/src/Moq/Mock.cs @@ -23,7 +23,7 @@ namespace Moq public abstract partial class Mock : IFluentInterface { internal static readonly MethodInfo GetMethod = - typeof(Mock).GetMethod(nameof(Get), BindingFlags.Public | BindingFlags.Static); + typeof(Mock).GetMethod(nameof(Get), BindingFlags.Public | BindingFlags.Static)!; /// /// Initializes a new instance of the class. @@ -151,7 +151,7 @@ public static void VerifyAll(params Mock[] mocks) /// public abstract bool CallBase { get; set; } - internal abstract object[] ConstructorArguments { get; } + internal abstract object?[] ConstructorArguments { get; } /// /// Specifies the behavior to use when returning default values for unexpected invocations on loose mocks. @@ -313,7 +313,7 @@ internal void Verify(Func predicate, HashSet verifiedMocks) } } - internal static void Verify(Mock mock, LambdaExpression expression, Times times, string failMessage) + internal static void Verify(Mock mock, LambdaExpression expression, Times times, string? failMessage) { Guard.NotNull(times, nameof(times)); @@ -333,7 +333,7 @@ internal static void Verify(Mock mock, LambdaExpression expression, Times times, } } - internal static void VerifyGet(Mock mock, LambdaExpression expression, Times times, string failMessage) + internal static void VerifyGet(Mock mock, LambdaExpression expression, Times times, string? failMessage) { Guard.NotNull(expression, nameof(expression)); @@ -346,7 +346,7 @@ internal static void VerifyGet(Mock mock, LambdaExpression expression, Times tim Mock.Verify(mock, expression, times, failMessage); } - internal static void VerifySet(Mock mock, LambdaExpression expression, Times times, string failMessage) + internal static void VerifySet(Mock mock, LambdaExpression expression, Times times, string? failMessage) { Guard.NotNull(expression, nameof(expression)); Guard.IsAssignmentToPropertyOrIndexer(expression, nameof(expression)); @@ -354,7 +354,7 @@ internal static void VerifySet(Mock mock, LambdaExpression expression, Times tim Mock.Verify(mock, expression, times, failMessage); } - internal static void VerifyAdd(Mock mock, LambdaExpression expression, Times times, string failMessage) + internal static void VerifyAdd(Mock mock, LambdaExpression expression, Times times, string? failMessage) { Guard.NotNull(expression, nameof(expression)); Guard.IsEventAdd(expression, nameof(expression)); @@ -362,7 +362,7 @@ internal static void VerifyAdd(Mock mock, LambdaExpression expression, Times tim Mock.Verify(mock, expression, times, failMessage); } - internal static void VerifyRemove(Mock mock, LambdaExpression expression, Times times, string failMessage) + internal static void VerifyRemove(Mock mock, LambdaExpression expression, Times times, string? failMessage) { Guard.NotNull(expression, nameof(expression)); Guard.IsEventRemove(expression, nameof(expression)); @@ -379,7 +379,7 @@ static void VerifyNoOtherCalls(Mock mock, HashSet verifiedMocks) { if (!verifiedMocks.Add(mock)) return; - var unverifiedInvocations = mock.MutableInvocations.ToArray(invocation => !invocation.IsVerified); + Invocation?[] unverifiedInvocations = mock.MutableInvocations.ToArray(invocation => !invocation.IsVerified); var innerMocks = mock.MutableSetups.FindAllInnerMocks(); @@ -397,7 +397,7 @@ static void VerifyNoOtherCalls(Mock mock, HashSet verifiedMocks) // In order for an invocation to be "transitive", its return value has to be a // sub-object (inner mock); and that sub-object has to have received at least // one call: - var wasTransitiveInvocation = mock.MutableSetups.FindLastInnerMock(setup => setup.Matches(unverifiedInvocations[i])) is Mock innerMock + var wasTransitiveInvocation = mock.MutableSetups.FindLastInnerMock(setup => setup.Matches(unverifiedInvocations[i]!)) is Mock innerMock && innerMock.MutableInvocations.Any(); if (wasTransitiveInvocation) { @@ -410,7 +410,7 @@ static void VerifyNoOtherCalls(Mock mock, HashSet verifiedMocks) var remainingUnverifiedInvocations = unverifiedInvocations.Where(i => i != null); if (remainingUnverifiedInvocations.Any()) { - throw MockException.UnverifiedInvocations(mock, remainingUnverifiedInvocations); + throw MockException.UnverifiedInvocations(mock, remainingUnverifiedInvocations!); } } @@ -493,7 +493,7 @@ static int GetMatchingInvocationCount( #region Setup - internal static MethodCall Setup(Mock mock, LambdaExpression expression, Condition condition) + internal static MethodCall Setup(Mock mock, LambdaExpression expression, Condition? condition) { Guard.NotNull(expression, nameof(expression)); @@ -505,7 +505,7 @@ internal static MethodCall Setup(Mock mock, LambdaExpression expression, Conditi }); } - internal static MethodCall SetupGet(Mock mock, LambdaExpression expression, Condition condition) + internal static MethodCall SetupGet(Mock mock, LambdaExpression expression, Condition? condition) { Guard.NotNull(expression, nameof(expression)); @@ -518,7 +518,7 @@ internal static MethodCall SetupGet(Mock mock, LambdaExpression expression, Cond return Mock.Setup(mock, expression, condition); } - internal static MethodCall SetupSet(Mock mock, LambdaExpression expression, Condition condition) + internal static MethodCall SetupSet(Mock mock, LambdaExpression expression, Condition? condition) { Guard.NotNull(expression, nameof(expression)); Guard.IsAssignmentToPropertyOrIndexer(expression, nameof(expression)); @@ -527,7 +527,7 @@ internal static MethodCall SetupSet(Mock mock, LambdaExpression expression, Cond } internal static readonly MethodInfo SetupReturnsMethod = - typeof(Mock).GetMethod(nameof(SetupReturns), BindingFlags.NonPublic | BindingFlags.Static); + typeof(Mock).GetMethod(nameof(SetupReturns), BindingFlags.NonPublic | BindingFlags.Static)!; // This specialized setup method is used to set up a single `Mock.Of` predicate. // Unlike other setup methods, LINQ to Mocks can set non-interceptable properties, which is handy when initializing DTOs. @@ -535,7 +535,7 @@ internal static bool SetupReturns(Mock mock, LambdaExpression expression, object { Guard.NotNull(expression, nameof(expression)); - Mock.SetupRecursive(mock, expression, setupLast: (targetMock, oe, part) => + Mock.SetupRecursive(mock, expression, setupLast: (targetMock, oe, part) => { var originalExpression = (LambdaExpression)oe; @@ -583,7 +583,7 @@ internal static bool SetupReturns(Mock mock, LambdaExpression expression, object return true; } - internal static MethodCall SetupAdd(Mock mock, LambdaExpression expression, Condition condition) + internal static MethodCall SetupAdd(Mock mock, LambdaExpression expression, Condition? condition) { Guard.NotNull(expression, nameof(expression)); Guard.IsEventAdd(expression, nameof(expression)); @@ -591,7 +591,7 @@ internal static MethodCall SetupAdd(Mock mock, LambdaExpression expression, Cond return Mock.Setup(mock, expression, condition); } - internal static MethodCall SetupRemove(Mock mock, LambdaExpression expression, Condition condition) + internal static MethodCall SetupRemove(Mock mock, LambdaExpression expression, Condition? condition) { Guard.NotNull(expression, nameof(expression)); Guard.IsEventRemove(expression, nameof(expression)); @@ -611,7 +611,7 @@ internal static SequenceSetup SetupSequence(Mock mock, LambdaExpression expressi }); } - internal static StubbedPropertySetup SetupProperty(Mock mock, LambdaExpression expression, object initialValue) + internal static StubbedPropertySetup SetupProperty(Mock mock, LambdaExpression expression, object? initialValue) { Guard.NotNull(expression, nameof(expression)); @@ -636,7 +636,7 @@ internal static StubbedPropertySetup SetupProperty(Mock mock, LambdaExpression e } static TSetup SetupRecursive(Mock mock, LambdaExpression expression, Func setupLast, bool allowNonOverridableLastProperty = false) - where TSetup : ISetup + where TSetup : ISetup? { Debug.Assert(mock != null); Debug.Assert(expression != null); @@ -647,7 +647,7 @@ static TSetup SetupRecursive(Mock mock, LambdaExpression expression, Fun } static TSetup SetupRecursive(Mock mock, LambdaExpression originalExpression, Stack parts, Func setupLast) - where TSetup : ISetup + where TSetup : ISetup? { var part = parts.Pop(); var (expr, method, arguments) = part; @@ -658,7 +658,7 @@ static TSetup SetupRecursive(Mock mock, LambdaExpression originalExpress } else { - Mock innerMock = mock.MutableSetups.FindLastInnerMock(setup => setup.Matches(part)); + Mock? innerMock = mock.MutableSetups.FindLastInnerMock(setup => setup.Matches(part)); if (innerMock == null) { var returnValue = mock.GetDefaultValue(method, out innerMock, useAlternateProvider: DefaultValueProvider.Mock); @@ -697,16 +697,18 @@ internal static void RaiseEvent(Mock mock, Action action, object[] argumen Mock.RaiseEvent(mock, expression, parts, arguments); } - internal static Task RaiseEventAsync(Mock mock, Action action, object[] arguments) + internal static Task RaiseEventAsync(Mock mock, Action action, object?[] arguments) { Guard.NotNull(action, nameof(action)); var expression = ExpressionReconstructor.Instance.ReconstructExpression(action, mock.ConstructorArguments); var parts = expression.Split(); + + // TODO: Will this code never return null? return (Task)Mock.RaiseEvent(mock, expression, parts, arguments); } - internal static object RaiseEvent(Mock mock, LambdaExpression expression, Stack parts, object[] arguments) + internal static object? RaiseEvent(Mock mock, LambdaExpression expression, Stack parts, object?[] arguments) { const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly; @@ -719,28 +721,22 @@ internal static object RaiseEvent(Mock mock, LambdaExpression expression, Stack< if (method.IsEventAddAccessor()) { var implementingMethod = method.GetImplementingMethod(mock.Object.GetType()); - @event = implementingMethod.DeclaringType.GetEvents(bindingFlags).SingleOrDefault(e => e.GetAddMethod(true) == implementingMethod); - if (@event == null) - { - throw new ArgumentException( - string.Format( - CultureInfo.CurrentCulture, - Resources.SetupNotEventAdd, - part.Expression)); - } + @event = implementingMethod.DeclaringType!.GetEvents(bindingFlags) + .SingleOrDefault(e => e.GetAddMethod(true) == implementingMethod) + ?? throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, + Resources.SetupNotEventAdd, + part.Expression)); + } else if (method.IsEventRemoveAccessor()) { var implementingMethod = method.GetImplementingMethod(mock.Object.GetType()); - @event = implementingMethod.DeclaringType.GetEvents(bindingFlags).SingleOrDefault(e => e.GetRemoveMethod(true) == implementingMethod); - if (@event == null) - { - throw new ArgumentException( - string.Format( - CultureInfo.CurrentCulture, - Resources.SetupNotEventRemove, - part.Expression)); - } + @event = implementingMethod.DeclaringType!.GetEvents(bindingFlags) + .SingleOrDefault(e => e.GetRemoveMethod(true) == implementingMethod) + ?? throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, + Resources.SetupNotEventRemove, + part.Expression)); + } else { @@ -754,7 +750,7 @@ internal static object RaiseEvent(Mock mock, LambdaExpression expression, Stack< if (mock.EventHandlers.TryGet(@event, out var handlers)) { var returnType = handlers.GetMethodInfo().ReturnType; - if (returnType == typeof(Task) || returnType == typeof(ValueTask)) + if (returnType == typeof(Task) || returnType.FullName == "System.Threading.Tasks.ValueTask") { var invocationList = handlers.GetInvocationList(); var tasks = new List(invocationList.Length); @@ -765,9 +761,12 @@ internal static object RaiseEvent(Mock mock, LambdaExpression expression, Stack< { tasks.Add(task); } - else if (returnValue is ValueTask valueTask) + else if (returnValue != null && returnValue.GetType().FullName == "System.Threading.Tasks.ValueTask") { - tasks.Add(valueTask.AsTask()); + // Use reflection to obtain .AsTask() to avoid embedding ValueTask type tokens + // in this method's IL for non-ValueTask execution paths. + var asTaskMethod = returnValue.GetType().GetMethod("AsTask", Type.EmptyTypes)!; + tasks.Add((Task)asTaskMethod.Invoke(returnValue, null)!); } } return Task.WhenAll(tasks); @@ -820,7 +819,7 @@ internal bool ImplementsInterface(Type interfaceType) #region Default Values - internal abstract Dictionary ConfiguredDefaultValues { get; } + internal abstract Dictionary ConfiguredDefaultValues { get; } /// /// Defines the default return value for all mocked methods or properties with return type . @@ -835,13 +834,13 @@ public void SetReturnsDefault(TReturn value) this.ConfiguredDefaultValues[typeof(TReturn)] = value; } - internal object GetDefaultValue(MethodInfo method, out Mock candidateInnerMock, DefaultValueProvider useAlternateProvider = null) + internal object? GetDefaultValue(MethodInfo method, out Mock? candidateInnerMock, DefaultValueProvider? useAlternateProvider = null) { Debug.Assert(method != null); Debug.Assert(method.ReturnType != null); Debug.Assert(method.ReturnType != typeof(void)); - if (this.ConfiguredDefaultValues.TryGetValue(method.ReturnType, out object configuredDefaultValue)) + if (this.ConfiguredDefaultValues.TryGetValue(method.ReturnType, out object? configuredDefaultValue)) { candidateInnerMock = null; return configuredDefaultValue; diff --git a/src/Moq/MockDefaultValueProvider.cs b/src/Moq/MockDefaultValueProvider.cs index ce97416b1..2458e9758 100644 --- a/src/Moq/MockDefaultValueProvider.cs +++ b/src/Moq/MockDefaultValueProvider.cs @@ -19,7 +19,7 @@ internal MockDefaultValueProvider() internal override DefaultValue Kind => DefaultValue.Mock; - protected override object GetFallbackDefaultValue(Type type, Mock mock) + protected override object? GetFallbackDefaultValue(Type type, Mock mock) { Debug.Assert(type != null); Debug.Assert(type != typeof(void)); @@ -34,7 +34,7 @@ protected override object GetFallbackDefaultValue(Type type, Mock mock) { // Create a new mock to be placed to InnerMocks dictionary if it's missing there var mockType = typeof(Mock<>).MakeGenericType(type); - Mock newMock = (Mock)Activator.CreateInstance(mockType, mock.Behavior); + Mock newMock = (Mock)Activator.CreateInstance(mockType, mock.Behavior)!; newMock.DefaultValueProvider = mock.DefaultValueProvider; if (mock.MutableSetups.FindLast(s => s is StubbedPropertiesSetup) is StubbedPropertiesSetup sts) { diff --git a/src/Moq/MockException.cs b/src/Moq/MockException.cs index 538a9349f..ebd608bc3 100644 --- a/src/Moq/MockException.cs +++ b/src/Moq/MockException.cs @@ -56,7 +56,7 @@ internal static MockException IncorrectNumberOfCalls(MethodCall setup, Times tim internal static MockException NoMatchingCalls( Mock rootMock, LambdaExpression expression, - string failMessage, + string? failMessage, Times times, int callCount) { @@ -172,7 +172,7 @@ internal static MockException FromInnerMockOf(ISetup setup, MockException error) /// and whose reason(s) is the combination of the given ' reason(s). /// Used by when it finds one or more mocks with verification errors. /// - internal static MockException Combined(IEnumerable errors, string preamble) + internal static MockException Combined(IEnumerable errors, string? preamble) { Debug.Assert(errors != null); Debug.Assert(errors.Any()); @@ -248,7 +248,7 @@ protected MockException( System.Runtime.Serialization.StreamingContext context) : base(info, context) { - this.reasons = (MockExceptionReasons)info.GetValue(nameof(this.reasons), typeof(MockExceptionReasons)); + this.reasons = (MockExceptionReasons)info.GetValue(nameof(this.reasons), typeof(MockExceptionReasons))!; } /// diff --git a/src/Moq/Mock`1.cs b/src/Moq/Mock`1.cs index d9e697875..36ca3ed3c 100644 --- a/src/Moq/Mock`1.cs +++ b/src/Moq/Mock`1.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using System.Text; @@ -84,10 +85,10 @@ static Mock() serialNumberCounter = 0; } - T instance; + T? instance; List additionalInterfaces; - Dictionary configuredDefaultValues; - object[] constructorArguments; + Dictionary configuredDefaultValues; + object?[] constructorArguments; DefaultValueProvider defaultValueProvider; EventHandlerCollection eventHandlers; InvocationCollection invocations; @@ -110,6 +111,7 @@ internal Mock(bool skipInitialize) // The skipInitialize parameter is not used at all, and it's // just to differentiate this ctor that should do nothing // from the regular ones which initializes the proxy, etc. + // TODO: How should nullable references be handled here? } /// @@ -176,7 +178,7 @@ public Mock(MockBehavior behavior, params object?[]? args) this.additionalInterfaces = new List(); this.behavior = behavior; - this.configuredDefaultValues = new Dictionary(); + this.configuredDefaultValues = new Dictionary(); this.constructorArguments = args; this.defaultValueProvider = DefaultValueProvider.Empty; this.eventHandlers = new EventHandlerCollection(); @@ -248,9 +250,9 @@ public override bool CallBase } } - internal override object[] ConstructorArguments => this.constructorArguments; + internal override object?[] ConstructorArguments => this.constructorArguments; - internal override Dictionary ConfiguredDefaultValues => this.configuredDefaultValues; + internal override Dictionary ConfiguredDefaultValues => this.configuredDefaultValues; /// /// Gets or sets the instance that will be used @@ -295,6 +297,9 @@ public override string ToString() return this.Name; } +#if NET + [MemberNotNull(nameof(instance))] +#endif void InitializeInstance() { // Determine the set of interfaces that the proxy object should additionally implement. @@ -623,7 +628,7 @@ public Mock SetupProperty(Expression> property) /// Assert.Equal(6, v.Value); /// /// - public Mock SetupProperty(Expression> property, TProperty initialValue) + public Mock SetupProperty(Expression> property, TProperty? initialValue) { Mock.SetupProperty(this, property, initialValue); return this; @@ -680,6 +685,11 @@ public ISetupSequentialAction SetupSequence(Expression> expression) /// public ISetupConditionResult When(Func condition) { + if (condition == null) + { + throw new ArgumentNullException(nameof(condition)); + } + return new WhenPhrase(this, new Condition(condition)); } @@ -1423,6 +1433,7 @@ public void Raise(Action eventExpression, params object[] args) /// public Task RaiseAsync(Action eventExpression, params object[] args) { + if (eventExpression.Method.ReturnType == typeof(void)) throw new ArgumentOutOfRangeException(nameof(eventExpression), "The event expression must return Task or ValueTask."); return Mock.RaiseEventAsync(this, eventExpression, args); } diff --git a/src/Moq/Moq.csproj b/src/Moq/Moq.csproj index 060a47377..93040a04c 100644 --- a/src/Moq/Moq.csproj +++ b/src/Moq/Moq.csproj @@ -6,11 +6,13 @@ $(NoWarn);0419;CS8032 true 4 - 10.0 + Latest - $(DefineConstants);FEATURE_DEFAULT_INTERFACE_IMPLEMENTATIONS + $(DefineConstants);NULLABLE_REFERENCE_TYPES;FEATURE_DEFAULT_INTERFACE_IMPLEMENTATIONS + enable + nullable @@ -35,7 +37,7 @@ - + diff --git a/src/Moq/Obsolete/MockFactory.cs b/src/Moq/Obsolete/MockFactory.cs index eb48d3892..023aeb4d1 100644 --- a/src/Moq/Obsolete/MockFactory.cs +++ b/src/Moq/Obsolete/MockFactory.cs @@ -222,7 +222,7 @@ public Mock Create() /// factory.Verify(); /// /// - public Mock Create(params object[] args) + public Mock Create(params object[]? args) where T : class { // "fix" compiler picking this overload instead of @@ -314,7 +314,7 @@ public Mock Create(Expression> newExpression, MockBehavior behavio /// Type to mock. /// The behavior for the new mock. /// Optional arguments for the construction of the mock. - protected virtual Mock CreateMock(MockBehavior behavior, object[] args) + protected virtual Mock CreateMock(MockBehavior behavior, object[]? args) where T : class { var mock = new Mock(behavior, args); diff --git a/src/Moq/Pair.cs b/src/Moq/Pair.cs index 973a73f62..c8e1e4133 100644 --- a/src/Moq/Pair.cs +++ b/src/Moq/Pair.cs @@ -28,7 +28,7 @@ public bool Equals(Pair other) && object.Equals(this.Item2, other.Item2); } - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is Pair other && this.Equals(other); } diff --git a/src/Moq/Protected/IProtectedAsMock.cs b/src/Moq/Protected/IProtectedAsMock.cs index 878faaf3b..8c1c9a060 100644 --- a/src/Moq/Protected/IProtectedAsMock.cs +++ b/src/Moq/Protected/IProtectedAsMock.cs @@ -89,7 +89,7 @@ public interface IProtectedAsMock : IFluentInterface /// Type of the property. Typically omitted as it can be inferred from the expression. /// Lambda expression that specifies the property. /// Initial value for the property. - Mock SetupProperty(Expression> expression, TProperty initialValue = default(TProperty)); + Mock SetupProperty(Expression> expression, TProperty? initialValue = default(TProperty)); /// /// Return a sequence of values, once per call. @@ -111,11 +111,11 @@ public interface IProtectedAsMock : IFluentInterface /// Lambda expression that specifies the method invocation. /// /// Number of times that the invocation is expected to have occurred. - /// If omitted, assumed to be . /// + /// If omitted, assumed to be . /// Message to include in the thrown if verification fails. /// The specified invocation did not occur (or did not occur the specified number of times). - void Verify(Expression> expression, Times? times = null, string failMessage = null); + void Verify(Expression> expression, Times? times = null, string? failMessage = null); /// /// Verifies that a specific invocation matching the given expression was performed on the mock. @@ -129,7 +129,7 @@ public interface IProtectedAsMock : IFluentInterface /// /// Message to include in the thrown if verification fails. /// The specified invocation did not occur (or did not occur the specified number of times). - void Verify(Expression> expression, Times? times = null, string failMessage = null); + void Verify(Expression> expression, Times? times = null, string? failMessage = null); /// /// Verifies that a property was set on the mock. @@ -143,7 +143,7 @@ public interface IProtectedAsMock : IFluentInterface /// /// The invocation was not called the number of times specified by . /// - void VerifySet(Action setterExpression, Times? times = null, string failMessage = null); + void VerifySet(Action setterExpression, Times? times = null, string? failMessage = null); /// /// Verifies that a property was read on the mock. @@ -156,6 +156,6 @@ public interface IProtectedAsMock : IFluentInterface /// /// Message to include in the thrown if verification fails. /// The specified invocation did not occur (or did not occur the specified number of times). - void VerifyGet(Expression> expression, Times? times = null, string failMessage = null); + void VerifyGet(Expression> expression, Times? times = null, string? failMessage = null); } } diff --git a/src/Moq/Protected/ItExpr.cs b/src/Moq/Protected/ItExpr.cs index 4e655bf42..e2e77633a 100644 --- a/src/Moq/Protected/ItExpr.cs +++ b/src/Moq/Protected/ItExpr.cs @@ -113,7 +113,7 @@ public static Expression IsAny() /// public static Expression Is(Expression> match) { - Expression> expr = () => It.Is((Expression>)null); + Expression> expr = () => It.Is((Expression>)null!); return Expression.Call(((MethodCallExpression)expr.Body).Method, match); } diff --git a/src/Moq/Protected/ProtectedAsMock.cs b/src/Moq/Protected/ProtectedAsMock.cs index c3d61024f..73b624a00 100644 --- a/src/Moq/Protected/ProtectedAsMock.cs +++ b/src/Moq/Protected/ProtectedAsMock.cs @@ -100,7 +100,7 @@ public ISetupGetter SetupGet(Expression(setup); } - public Mock SetupProperty(Expression> expression, TProperty initialValue = default(TProperty)) + public Mock SetupProperty(Expression> expression, TProperty? initialValue = default(TProperty)) { Guard.NotNull(expression, nameof(expression)); @@ -153,7 +153,7 @@ public ISetupSequentialAction SetupSequence(Expression> expressi return new SetupSequencePhrase(setup); } - public void Verify(Expression> expression, Times? times = null, string failMessage = null) + public void Verify(Expression> expression, Times? times = null, string? failMessage = null) { Guard.NotNull(expression, nameof(expression)); @@ -170,7 +170,7 @@ public void Verify(Expression> expression, Times? times = null, Mock.Verify(this.mock, rewrittenExpression, times ?? Times.AtLeastOnce(), failMessage); } - public void Verify(Expression> expression, Times? times = null, string failMessage = null) + public void Verify(Expression> expression, Times? times = null, string? failMessage = null) { Guard.NotNull(expression, nameof(expression)); @@ -187,7 +187,7 @@ public void Verify(Expression> expression, Times Mock.Verify(this.mock, rewrittenExpression, times ?? Times.AtLeastOnce(), failMessage); } - public void VerifySet(Action setterExpression, Times? times = null, string failMessage = null) + public void VerifySet(Action setterExpression, Times? times = null, string? failMessage = null) { Guard.NotNull(setterExpression, nameof(setterExpression)); @@ -195,7 +195,7 @@ public void VerifySet(Action setterExpression, Times? times = null, str Mock.VerifySet(mock, rewrittenExpression, times.HasValue ? times.Value : Times.AtLeastOnce(), failMessage); } - public void VerifyGet(Expression> expression, Times? times = null, string failMessage = null) + public void VerifyGet(Expression> expression, Times? times = null, string? failMessage = null) { Guard.NotNull(expression, nameof(expression)); diff --git a/src/Moq/Protected/ProtectedMock.cs b/src/Moq/Protected/ProtectedMock.cs index d6c8e808f..2007312f6 100644 --- a/src/Moq/Protected/ProtectedMock.cs +++ b/src/Moq/Protected/ProtectedMock.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Linq.Expressions; @@ -51,7 +52,7 @@ public ISetup Setup(string methodName, Type[] genericTypeArguments, bool exac return this.InternalSetup(methodName, genericTypeArguments, exactParameterMatch, args); } - ISetup InternalSetup(string methodName, Type[] genericTypeArguments, bool exactParameterMatch, params object[] args) + ISetup InternalSetup(string methodName, Type[]? genericTypeArguments, bool exactParameterMatch, params object[] args) { Guard.NotNull(methodName, nameof(methodName)); @@ -80,7 +81,7 @@ public ISetup Setup(string methodName, Type[] genericTypeAr return this.InternalSetup(methodName, genericTypeArguments, exactParameterMatch, args); } - ISetup InternalSetup(string methodName, Type[] genericTypeArguments, + ISetup InternalSetup(string methodName, Type[]? genericTypeArguments, bool exactParameterMatch, params object[] args) { Guard.NotNullOrEmpty(methodName, nameof(methodName)); @@ -147,7 +148,7 @@ public ISetupSequentialAction SetupSequence(string methodOrPropertyName, Type[] return this.InternalSetupSequence(methodOrPropertyName, genericTypeArguments, exactParameterMatch, args); } - ISetupSequentialAction InternalSetupSequence(string methodOrPropertyName, Type[] genericTypeArguments, bool exactParameterMatch, params object[] args) + ISetupSequentialAction InternalSetupSequence(string methodOrPropertyName, Type[]? genericTypeArguments, bool exactParameterMatch, params object[] args) { Guard.NotNullOrEmpty(methodOrPropertyName, nameof(methodOrPropertyName)); @@ -175,7 +176,7 @@ public ISetupSequentialResult SetupSequence(string methodOrPro return this.InternalSetupSequence(methodOrPropertyName, genericTypeArguments, exactParameterMatch, args); } - ISetupSequentialResult InternalSetupSequence(string methodOrPropertyName, Type[] genericTypeArguments, bool exactParameterMatch, params object[] args) + ISetupSequentialResult InternalSetupSequence(string methodOrPropertyName, Type[]? genericTypeArguments, bool exactParameterMatch, params object[] args) { Guard.NotNullOrEmpty(methodOrPropertyName, nameof(methodOrPropertyName)); @@ -223,7 +224,7 @@ public void Verify(string methodName, Type[] genericTypeArguments, Times times, this.InternalVerify(methodName, genericTypeArguments, times, exactParameterMatch, args); } - void InternalVerify(string methodName, Type[] genericTypeArguments, Times times, bool exactParameterMatch, params object[] args) + void InternalVerify(string methodName, Type[]? genericTypeArguments, Times times, bool exactParameterMatch, params object[] args) { Guard.NotNullOrEmpty(methodName, nameof(methodName)); @@ -256,7 +257,7 @@ public void Verify(string methodName, Type[] genericTypeArguments, Time this.InternalVerify(methodName, genericTypeArguments, times, exactParameterMatch, args); } - void InternalVerify(string methodName, Type[] genericTypeArguments, Times times, bool exactParameterMatch, params object[] args) + void InternalVerify(string methodName, Type[]? genericTypeArguments, Times times, bool exactParameterMatch, params object[] args) { Guard.NotNullOrEmpty(methodName, nameof(methodName)); @@ -314,7 +315,7 @@ static Expression> GetMemberAccess(PropertyInfo proper return Expression.Lambda>(Expression.MakeMemberAccess(param, property), param); } - static MethodInfo GetMethod(string methodName, Type[] genericTypeArguments, bool exact, params object[] args) + static MethodInfo? GetMethod(string methodName, Type[]? genericTypeArguments, bool exact, params object[] args) { var argTypes = ToArgTypes(args); var methods = typeof(T).GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public) @@ -343,7 +344,7 @@ static Expression> GetMethodCall(MethodInfo method, object[] args) } // TODO should support arguments for property indexers - static PropertyInfo GetProperty(string propertyName) + static PropertyInfo? GetProperty(string propertyName) { return typeof(T).GetProperty( propertyName, @@ -359,7 +360,11 @@ static Expression> GetSetterExpression(PropertyInfo property, Expressi param); } - static void ThrowIfMemberMissing(string memberName, MemberInfo member) +#if NULLABLE_REFERENCE_TYPES + static void ThrowIfMemberMissing(string memberName, [NotNull] MemberInfo? member) +#else + static void ThrowIfMemberMissing(string memberName, MemberInfo? member) +#endif { if (member == null) { @@ -371,7 +376,11 @@ static void ThrowIfMemberMissing(string memberName, MemberInfo member) } } - static void ThrowIfMethodMissing(string methodName, MethodInfo method, object[] args) +#if NULLABLE_REFERENCE_TYPES + static void ThrowIfMethodMissing(string methodName, [NotNull] MethodInfo? method, object[] args) +#else + static void ThrowIfMethodMissing(string methodName, MethodInfo? method, object[] args) +#endif { if (method == null) { @@ -443,14 +452,14 @@ static void ThrowIfVoidMethod(MethodInfo method) } } - static Type[] ToArgTypes(object[] args) + static Type?[] ToArgTypes(object[] args) { if (args == null) { throw new ArgumentException(Resources.UseItExprIsNullRatherThanNullArgumentValue); } - var types = new Type[args.Length]; + var types = new Type?[args.Length]; for (int index = 0; index < args.Length; index++) { if (args[index] == null) @@ -503,9 +512,9 @@ static bool IsItRefAny(Expression expression) return ItRefAnyField(expression) != null; } - static FieldInfo ItRefAnyField(Expression expr) + static FieldInfo? ItRefAnyField(Expression expr) { - FieldInfo itRefAnyField = null; + FieldInfo? itRefAnyField = null; if (expr.NodeType == ExpressionType.MemberAccess) { @@ -514,7 +523,7 @@ static FieldInfo ItRefAnyField(Expression expr) { if (field.Name == nameof(It.Ref.IsAny)) { - var fieldDeclaringType = field.DeclaringType; + var fieldDeclaringType = field.DeclaringType!; if (fieldDeclaringType.IsGenericType) { var fieldDeclaringTypeDefinition = fieldDeclaringType.GetGenericTypeDefinition(); diff --git a/src/Moq/ReturnsExtensions.cs b/src/Moq/ReturnsExtensions.cs index 5247edcb5..dd61fd754 100644 --- a/src/Moq/ReturnsExtensions.cs +++ b/src/Moq/ReturnsExtensions.cs @@ -3,6 +3,7 @@ using System; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Moq.Language; @@ -48,11 +49,11 @@ public static IReturnsResult ReturnsAsync(this IReturnsType of the return value. /// Returns verb which represents the mocked type and the task of return type /// The function that will calculate the return value. - public static IReturnsResult ReturnsAsync(this IReturns> mock, Func valueFunction) where TMock : class + public static IReturnsResult ReturnsAsync(this IReturns> mock, Func? valueFunction) where TMock : class { if (IsNullResult(valueFunction, typeof(TResult))) { - return mock.ReturnsAsync(() => default); + return mock.ReturnsAsync(() => default!); } return mock.Returns(() => Task.FromResult(valueFunction())); @@ -69,7 +70,7 @@ public static IReturnsResult ReturnsAsync(this IReturns default); + return mock.ReturnsAsync(() => default!); } return mock.Returns(() => new ValueTask(valueFunction())); @@ -283,7 +284,11 @@ public static IReturnsResult ThrowsAsync(this IReturns null; + public virtual Condition? Condition => null; public Expectation Expectation => this.expectation; public LambdaExpression Expression => this.expectation.Expression; - Mock ISetup.InnerMock => this.InnerMocks.SingleOrDefault(); + Mock? ISetup.InnerMock => this.InnerMocks.SingleOrDefault(); public virtual IEnumerable InnerMocks => Enumerable.Empty(); @@ -47,7 +47,7 @@ protected Setup(Expression originalExpression, Mock mock, Expectation expectatio public Mock Mock => this.mock; - public Expression OriginalExpression => this.originalExpression; + public Expression? OriginalExpression => this.originalExpression; public bool IsMatched => (this.flags & Flags.Matched) != 0; @@ -204,7 +204,7 @@ void Verify(bool recursive, Func predicate) this.Verify(recursive, predicate, verifiedMocks); } - protected static Mock TryGetInnerMockFrom(object returnValue) + protected static Mock? TryGetInnerMockFrom(object? returnValue) { return (Awaitable.TryGetResultRecursive(returnValue) as IMocked)?.Mock; } diff --git a/src/Moq/SetupCollection.cs b/src/Moq/SetupCollection.cs index 7c210c9a6..c21907bfb 100644 --- a/src/Moq/SetupCollection.cs +++ b/src/Moq/SetupCollection.cs @@ -101,7 +101,7 @@ public List FindAll(Func predicate) return setups; } - public Setup FindLast(Func predicate) + public Setup? FindLast(Func predicate) { // Fast path (no `lock`) when there are no setups: if (this.setups.Count == 0) diff --git a/src/Moq/SetupWithOutParameterSupport.cs b/src/Moq/SetupWithOutParameterSupport.cs index d08ee3518..eabf6dd35 100644 --- a/src/Moq/SetupWithOutParameterSupport.cs +++ b/src/Moq/SetupWithOutParameterSupport.cs @@ -13,9 +13,9 @@ namespace Moq { abstract class SetupWithOutParameterSupport : MethodSetup { - readonly List> outValues; + readonly List>? outValues; - protected SetupWithOutParameterSupport(Expression originalExpression, Mock mock, MethodExpectation expectation) + protected SetupWithOutParameterSupport(Expression? originalExpression, Mock mock, MethodExpectation expectation) : base(originalExpression, mock, expectation) { Debug.Assert(expectation != null); @@ -34,9 +34,9 @@ public sealed override void SetOutParameters(Invocation invocation) } } - static List> GetOutValues(IReadOnlyList arguments, ParameterInfo[] parameters) + static List>? GetOutValues(IReadOnlyList arguments, ParameterInfo[] parameters) { - List> outValues = null; + List>? outValues = null; for (int i = 0, n = parameters.Length; i < n; ++i) { var parameter = parameters[i]; @@ -51,10 +51,10 @@ static List> GetOutValues(IReadOnlyList ar if (outValues == null) { - outValues = new List>(); + outValues = new List>(); } - outValues.Add(new KeyValuePair(i, constant.Value)); + outValues.Add(new KeyValuePair(i, constant.Value)); } } } diff --git a/src/Moq/StringBuilderExtensions.AppendExpression.cs b/src/Moq/StringBuilderExtensions.AppendExpression.cs index 5204b502b..518e52038 100644 --- a/src/Moq/StringBuilderExtensions.AppendExpression.cs +++ b/src/Moq/StringBuilderExtensions.AppendExpression.cs @@ -17,7 +17,7 @@ namespace Moq // These methods are intended to create more readable string representations for use in failure messages. partial class StringBuilderExtensions { - public static StringBuilder AppendExpression(this StringBuilder builder, Expression expression) + public static StringBuilder AppendExpression(this StringBuilder builder, Expression? expression) { if (expression == null) { @@ -273,7 +273,7 @@ static StringBuilder AppendExpression(this StringBuilder builder, MemberExpressi } else { - builder.AppendNameOf(expression.Member.DeclaringType); + builder.AppendNameOf(expression.Member.DeclaringType!); } return builder.Append('.') @@ -300,7 +300,7 @@ static StringBuilder AppendExpression(this StringBuilder builder, MethodCallExpr { Debug.Assert(method.IsStatic); - builder.AppendNameOf(method.DeclaringType); + builder.AppendNameOf(method.DeclaringType!); } if (method.IsGetAccessor()) @@ -380,7 +380,7 @@ static StringBuilder AppendExpression(this StringBuilder builder, LambdaExpressi static StringBuilder AppendExpression(this StringBuilder builder, NewExpression expression) { - Type type = (expression.Constructor == null) ? expression.Type : expression.Constructor.DeclaringType; + Type type = (expression.Constructor == null) ? expression.Type : expression.Constructor.DeclaringType!; return builder.Append("new ") .AppendNameOf(type) .AppendCommaSeparated("(", expression.Arguments, AppendExpression, ")"); @@ -395,7 +395,7 @@ static StringBuilder AppendExpression(this StringBuilder builder, NewArrayExpres case ExpressionType.NewArrayBounds: return builder.Append("new ") - .AppendNameOf(expression.Type.GetElementType()) + .AppendNameOf(expression.Type.GetElementType()!) .AppendCommaSeparated("[", expression.Expressions, AppendExpression, "]"); } diff --git a/src/Moq/StringBuilderExtensions.cs b/src/Moq/StringBuilderExtensions.cs index 127e665b1..939d0bd53 100644 --- a/src/Moq/StringBuilderExtensions.cs +++ b/src/Moq/StringBuilderExtensions.cs @@ -95,7 +95,7 @@ public static StringBuilder AppendParameterType(this StringBuilder stringBuilder _ => "ref ", }); - parameterType = parameterType.GetElementType(); + parameterType = parameterType.GetElementType()!; } if (parameterType.IsArray && parameter.IsDefined(typeof(ParamArrayAttribute), true)) @@ -106,7 +106,7 @@ public static StringBuilder AppendParameterType(this StringBuilder stringBuilder return stringBuilder.AppendFormattedName(parameterType); } - public static StringBuilder AppendValueOf(this StringBuilder stringBuilder, object obj) + public static StringBuilder AppendValueOf(this StringBuilder stringBuilder, object? obj) { if (obj == null) { @@ -128,12 +128,11 @@ public static StringBuilder AppendValueOf(this StringBuilder stringBuilder, obje { stringBuilder.AppendNameOf(obj.GetType()).Append('.').Append(obj); } - else if (obj.GetType().IsArray || (obj.GetType().IsConstructedGenericType && obj.GetType().GetGenericTypeDefinition() == typeof(List<>))) + else if (obj is IReadOnlyList list) { stringBuilder.Append('['); const int maxCount = 10; - var enumerator = ((IEnumerable)obj).GetEnumerator(); - for (int i = 0; enumerator.MoveNext() && i < maxCount + 1; ++i) + for (var i = 0; i < list.Count; i++) { if (i > 0) { @@ -146,8 +145,9 @@ public static StringBuilder AppendValueOf(this StringBuilder stringBuilder, obje break; } - stringBuilder.AppendValueOf(enumerator.Current); + stringBuilder.AppendValueOf(list[i]); } + stringBuilder.Append(']'); } else diff --git a/src/Moq/StubbedPropertiesSetup.cs b/src/Moq/StubbedPropertiesSetup.cs index 7350b9a6b..15620cf12 100644 --- a/src/Moq/StubbedPropertiesSetup.cs +++ b/src/Moq/StubbedPropertiesSetup.cs @@ -12,13 +12,13 @@ namespace Moq { sealed class StubbedPropertiesSetup : Setup { - readonly ConcurrentDictionary values; + readonly ConcurrentDictionary values; readonly DefaultValueProvider defaultValueProvider; - public StubbedPropertiesSetup(Mock mock, DefaultValueProvider defaultValueProvider = null) + public StubbedPropertiesSetup(Mock mock, DefaultValueProvider? defaultValueProvider = null) : base(originalExpression: null, mock, new PropertyAccessorExpectation(mock)) { - this.values = new ConcurrentDictionary(); + this.values = new ConcurrentDictionary(); this.defaultValueProvider = defaultValueProvider ?? mock.DefaultValueProvider; this.MarkAsVerifiable(); @@ -79,7 +79,7 @@ public PropertyAccessorExpectation(Mock mock) Debug.Assert(mock != null); var mockType = mock.GetType(); - var setupAllPropertiesMethod = mockType.GetMethod(nameof(Mock.SetupAllProperties)); + var setupAllPropertiesMethod = mockType.GetMethod(nameof(Mock.SetupAllProperties))!; var mockedType = setupAllPropertiesMethod.ReturnType.GetGenericArguments()[0]; var mockGetMethod = Mock.GetMethod.MakeGenericMethod(mockedType); var mockParam = E.Parameter(mockedType, "m"); @@ -88,7 +88,7 @@ public PropertyAccessorExpectation(Mock mock) public override LambdaExpression Expression => this.expression; - public override bool Equals(Expectation other) + public override bool Equals(Expectation? other) { return other is PropertyAccessorExpectation pae && ExpressionComparer.Default.Equals(this.expression, pae.expression); } diff --git a/src/Moq/StubbedPropertySetup.cs b/src/Moq/StubbedPropertySetup.cs index eb1bb2283..0bb7ade60 100644 --- a/src/Moq/StubbedPropertySetup.cs +++ b/src/Moq/StubbedPropertySetup.cs @@ -10,9 +10,9 @@ namespace Moq { sealed class StubbedPropertySetup : Setup { - object value; + object? value; - public StubbedPropertySetup(Mock mock, LambdaExpression expression, MethodInfo getter, MethodInfo setter, object initialValue) + public StubbedPropertySetup(Mock mock, LambdaExpression expression, MethodInfo? getter, MethodInfo? setter, object? initialValue) : base(originalExpression: null, mock, new PropertyAccessorExpectation(expression, getter, setter)) { // NOTE: @@ -70,10 +70,10 @@ protected override void VerifySelf() sealed class PropertyAccessorExpectation : Expectation { readonly LambdaExpression expression; - readonly MethodInfo getter; - readonly MethodInfo setter; + readonly MethodInfo? getter; + readonly MethodInfo? setter; - public PropertyAccessorExpectation(LambdaExpression expression, MethodInfo getter, MethodInfo setter) + public PropertyAccessorExpectation(LambdaExpression expression, MethodInfo? getter, MethodInfo? setter) { Debug.Assert(expression != null); Debug.Assert(expression.IsProperty()); @@ -86,7 +86,7 @@ public PropertyAccessorExpectation(LambdaExpression expression, MethodInfo gette public override LambdaExpression Expression => this.expression; - public override bool Equals(Expectation obj) + public override bool Equals(Expectation? obj) { return obj is PropertyAccessorExpectation other && other.getter == this.getter @@ -101,7 +101,7 @@ public override int GetHashCode() public override bool IsMatch(Invocation invocation) { var methodName = invocation.Method.Name; - return methodName == this.getter.Name || methodName == this.setter.Name; + return methodName == this.getter?.Name || methodName == this.setter?.Name; } } } diff --git a/src/Moq/Times.cs b/src/Moq/Times.cs index e92ac06dd..1906d01ae 100644 --- a/src/Moq/Times.cs +++ b/src/Moq/Times.cs @@ -192,7 +192,7 @@ public bool Equals(Times other) /// if has the same value as this instance; /// otherwise, . /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is Times other && this.Equals(other); } diff --git a/src/Moq/TypeMatcherAttribute.cs b/src/Moq/TypeMatcherAttribute.cs index 7e7db6402..96a0a6834 100644 --- a/src/Moq/TypeMatcherAttribute.cs +++ b/src/Moq/TypeMatcherAttribute.cs @@ -16,7 +16,7 @@ namespace Moq [AttributeUsage(AttributeTargets.Class | AttributeTargets.Delegate | AttributeTargets.Enum | AttributeTargets.Interface | AttributeTargets.Struct, AllowMultiple = false, Inherited = true)] public class TypeMatcherAttribute : Attribute { - readonly Type type; + readonly Type? type; /// /// Initializes a new instance of the class. @@ -39,14 +39,9 @@ public TypeMatcherAttribute() /// The of a type that implements . public TypeMatcherAttribute(Type type) { - if (type == null) - { - throw new ArgumentNullException(nameof(type)); - } - - this.type = type; + this.type = type ?? throw new ArgumentNullException(nameof(type)); } - internal Type Type => this.type; + internal Type? Type => this.type; } }