diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 000000000..38ac18578 --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,24 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "powershell": { + "version": "7.4.5", + "commands": [ + "pwsh" + ] + }, + "dotnet-coverage": { + "version": "17.12.6", + "commands": [ + "dotnet-coverage" + ] + }, + "nbgv": { + "version": "3.6.146", + "commands": [ + "nbgv" + ] + } + } +} diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 6ed7f6e71..9626b31b5 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,5 +1,5 @@ # Refer to https://hub.docker.com/_/microsoft-dotnet-sdk for available versions -FROM mcr.microsoft.com/dotnet/sdk:8.0.300-jammy +FROM mcr.microsoft.com/dotnet/sdk:8.0.402-jammy # Installing mono makes `dotnet test` work without errors even for net472. # But installing it takes a long time, so it's excluded by default. diff --git a/.editorconfig b/.editorconfig index c99b5fb73..e84d0f554 100644 --- a/.editorconfig +++ b/.editorconfig @@ -26,12 +26,17 @@ indent_size = 2 # Xml config files [*.{ruleset,config,nuspec,resx,vsixmanifest,vsct,runsettings}] indent_size = 2 +indent_style = space # JSON files [*.json] indent_size = 2 indent_style = space +[*.ps1] +indent_style = space +indent_size = 4 + # Dotnet code style settings: [*.{cs,vb}] @@ -44,6 +49,7 @@ dotnet_diagnostic.SA1519.severity = silent # Sort using and Import directives with System.* appearing first dotnet_sort_system_directives_first = true +dotnet_separate_import_directive_groups = false dotnet_style_qualification_for_field = false:silent dotnet_style_qualification_for_property = false:silent dotnet_style_qualification_for_method = false:silent @@ -186,6 +192,9 @@ dotnet_diagnostic.DOC108.severity = warning dotnet_diagnostic.DOC200.severity = warning dotnet_diagnostic.DOC202.severity = warning +# CA1062: Validate arguments of public methods +dotnet_diagnostic.CA1062.severity = suggestion + # CS1591: Missing XML comment for publicly visible type or member dotnet_diagnostic.CS1591.severity = suggestion @@ -234,5 +243,23 @@ dotnet_diagnostic.SA1615.severity = suggestion # SA1618: Document type parameters dotnet_diagnostic.SA1618.severity = suggestion +# CA2016: Forward the CancellationToken parameter +dotnet_diagnostic.CA2016.severity = warning + +# SA1005: Single line comments should begin with single space +dotnet_diagnostic.SA1005.severity = none + +# SA1507: Code should not contain multiple blank lines in a row +dotnet_diagnostic.SA1507.severity = none + +# SA1508: Closing braces should not be preceded by blank line +dotnet_diagnostic.SA1508.severity = none + +# SA1512: Single-line comments should not be followed by blank line +dotnet_diagnostic.SA1512.severity = none + +# SA1515: Single-line comment should be preceded by blank line +dotnet_diagnostic.SA1515.severity = none + [*.sln] indent_style = tab diff --git a/.github/actions/check-metas/action.yaml b/.github/actions/check-metas/action.yaml new file mode 100644 index 000000000..7966a217b --- /dev/null +++ b/.github/actions/check-metas/action.yaml @@ -0,0 +1,33 @@ +name: Check all .meta is commited +description: Check all Unity .meta files are committed. +inputs: + directory: + description: Working directory to check change + required: true + exit-on-error: + description: Exit on error + required: false + default: "true" +outputs: + meta-exists: + description: If .meta file exists + value: ${{ steps.check-meta.outputs.meta-exists }} + +runs: + using: "composite" + steps: + - name: Check .meta exists + id: check-meta + shell: bash + run: | + if git ls-files --others --exclude-standard -t | grep --regexp='[.]meta$'; then + echo "Detected .meta file generated. Do you forgot commit a .meta file?" + echo "meta-exists=true" | tee -a "$GITHUB_OUTPUT" + if [[ "${{inputs.exit-on-error }}" == "true" ]]; then + exit 1 + fi + else + echo "Great, all .meta files are commited." + echo "meta-exists=false" | tee -a "$GITHUB_OUTPUT" + fi + working-directory: ${{ inputs.directory }} diff --git a/.github/actions/setup-dotnet/action.yaml b/.github/actions/setup-dotnet/action.yaml new file mode 100644 index 000000000..e44880788 --- /dev/null +++ b/.github/actions/setup-dotnet/action.yaml @@ -0,0 +1,52 @@ +name: Setup .NET SDKs +description: Setup .NET SDKs and environment variables +inputs: + global-json-file: + description: "path to the global.json file." + required: false + default: "global.json" + skip-env: + description: "Optional skip environment set." + required: false + default: "false" + +runs: + using: "composite" + steps: + # see: https://github.com/actions/setup-dotnet + - uses: actions/setup-dotnet@v4 + with: + global-json-file: ${{ inputs.global-json-file }} + + # see: https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-environment-variables + # GitHub Actions Hosted Runner default environment contains some .NET Environment. + # * DOTNET_MUTILEVEL_LOOKUP=0 + # * DOTNET_NOLOGO=1 + # * DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 + # Do not set the following environment variables: + # * NUGET_XMLDOC_MODE=skip <- pulumi and some other packge restore will fail. + - name: "Configure Environment Variables (.NET SDK)" + shell: bash + run: | + echo "::group::Configure .NET Environment Variables." + echo "COMPlus_EnableDiagnostics=0" | tee -a "$GITHUB_ENV" + echo "DOTNET_CLI_TELEMETRY_OPTOUT=1" | tee -a "$GITHUB_ENV" + echo "DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION=1" | tee -a "$GITHUB_ENV" + echo "MSBUILDDISABLENODEREUSE=1" | tee -a "$GITHUB_ENV" + echo "TERM=xterm" | tee -a "$GITHUB_ENV" + echo "::endgroup::" + if: ${{ inputs.skip-env == 'false' }} + + - name: dotnet version + shell: bash + run: | + echo "::group::Show dotnet version" + dotnet --version + echo "::endgroup::" + + - name: List installed .NET SDKs + shell: bash + run: | + echo "::group::List installed .NET SDKs" + dotnet --list-sdks + echo "::endgroup::" diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 7150f8e07..718f2612c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,5 +1,5 @@ # Please see the documentation for all configuration options: -# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file version: 2 updates: diff --git a/.github/workflows/_clean-packagejson-branch.yaml b/.github/workflows/_clean-packagejson-branch.yaml new file mode 100644 index 000000000..864f968df --- /dev/null +++ b/.github/workflows/_clean-packagejson-branch.yaml @@ -0,0 +1,54 @@ +name: (R) Clean package.json branch + +on: + workflow_call: + inputs: + branch: + description: "branch name to delete. Only delete branches that are created by github-actions[bot] and are not the default branch." + required: true + type: string + outputs: + branch-deleted: + description: "Indicate branch is deleted or not by boolean. true = branch deleted, false = branch not deleted." + value: ${{ jobs.cleanup.outputs.branch-deleted }} + +jobs: + cleanup: + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # auto generated token + runs-on: ubuntu-latest + timeout-minutes: 5 + outputs: + branch-deleted: ${{ steps.check-branch.outputs.deletable }} + steps: + - name: Check branch is deletable + id: check-branch + run: | + # Check if the branch is the default branch + if [[ "$(gh api /repos/${{ github.repository }} | jq -r '.default_branch')" == "${{ inputs.branch }}" ]]; then + echo "Branch is default, you cannot delete this branch. branch: ${{ inputs.branch }}" + exit 1 + fi + + # Check if the branch is created by github-actions[bot] + if gh api /repos/${{ github.repository }}/branches | jq -r '.[].name' | grep "${{ inputs.branch }}" >/dev/null; then + echo "branch exists. branch: ${{ inputs.branch }}" + + # Check info of the branch + gh api /repos/${{ github.repository }}/branches/${{ inputs.branch }} | jq + + if [[ "$(gh api /repos/${{ github.repository }}/branches/${{ inputs.branch }} | jq -r '.commit.author.login')" != "github-actions[bot]" ]]; then + echo "Branch is not created by github-actions[bot], you cannot delete this branch. branch: ${{ inputs.branch }}" + exit 1 + fi + + branch_deletable=true + else + echo "branch not exists. branch: ${{ inputs.branch }}" + branch_deletable=false + fi + + echo "deletable=${branch_deletable}" | tee -a "${GITHUB_OUTPUT}" + - name: Delete branch + if: ${{ steps.check-branch.outputs.deletable == 'true' }} + run: gh api -X DELETE /repos/${{ github.repository }}/git/refs/heads/${{ inputs.branch }} diff --git a/.github/workflows/_create-release.yaml b/.github/workflows/_create-release.yaml new file mode 100644 index 000000000..ec5728191 --- /dev/null +++ b/.github/workflows/_create-release.yaml @@ -0,0 +1,169 @@ +name: (R) Create Release + +on: + workflow_call: + inputs: + commit-id: + description: "CommitId to create release & tag." + required: true + type: string + dry-run: + description: "true = no upload. false = dry run changes && delete release after 60s." + required: true + type: boolean + tag: + description: "Git tag to create. (sample 1.0.0)" + required: true + type: string + # nuget + nuget-path: + description: "nuget path to upload." + required: false + type: string + default: | + ./nuget/*.nupkg + ./nuget/*.snupkg + nuget-push: + description: "true = upload nuget package. false = not upload" + required: false + type: boolean + default: false + # release + release-asset-path: + description: "release assets path to upload. This is a list of paths separated by a newline." + required: false + type: string + release-upload: + description: "true = upload assets. false = not upload" + required: false + type: boolean + default: false + +jobs: + create-release: + name: Create Release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # auto generated token + GH_REPO: ${{ github.repository }} + NUGET_KEY: ${{ secrets.NUGET_KEY }} + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Validate inputs - unitypackage + shell: bash + if: ${{ inputs.release-upload && inputs.release-asset-path == '' }} + run: | + echo "Validation error! 'inputs.release-asset-path' cannot be blank when 'inputs.release-upload' is true." + exit 1 + + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.commit-id }} + - uses: ./.github/actions/setup-dotnet + + # Download(All) Artifacts to $GITHUB_WORKSPACE + - name: donload artifacts + uses: actions/download-artifact@v4 # must sync with actions/upload-artifact@v4 in build-release + - name: Show download aritifacts + run: ls -lR + - name: Validate package exists in artifact - release assets + if: ${{ inputs.release-upload }} + run: | + while read -r asset_path; do + if [[ "${asset_path}" == "" ]]; then continue; fi + # is it a wildcard? + # shellcheck disable=SC2086 + if [[ "$asset_path" == *\** || "$asset_path" == *\?* ]]; then + # shellcheck disable=SC2086 + if ! ls -l ${asset_path}; then + echo "Specified nuget package not found. path: $asset_path" + if [[ "${asset_path}" == *.nupkg ]]; then + echo ".nupkg must be included in the artifact." + exit 1 + fi + fi + continue + fi + # is it a file? + if [[ ! -f "${asset_path}" ]]; then + echo "Specified asset not found. path: ${asset_path}" + exit 1 + fi + done <<< "${{ inputs.release-asset-path }}" + - name: Validate package exists in artifact - NuGet + if: ${{ inputs.nuget-push }} + run: | + while read -r nuget_path; do + if [[ "${nuget_path}" == "" ]]; then continue; fi + # shellcheck disable=SC2086 + if ! ls -l ${nuget_path}; then + echo "Specified nuget package not found. path: $nuget_path" + if [[ "${nuget_path}" == *.nupkg ]]; then + echo ".nupkg must be included in the artifact." + exit 1 + fi + fi + done <<< "${{ inputs.nuget-path }}" + + # Create Releases + - name: Create Tag + run: | + git tag ${{ inputs.tag }} + git push origin ${{ inputs.tag }} + - name: Create Release + run: gh release create ${{ inputs.tag }} --draft --verify-tag --title "${{ inputs.tag }}" --generate-notes + - name: Wait and Verify Release Name is expected + run: | + sleep 5s + actual=$(gh api --paginate /repos/${{ github.repository }}/releases?per_page=100 --jq '.[] | select(.tag_name == "${{ inputs.tag }}") | .name') + expected="${{ inputs.tag }}" + if [[ "$actual" != "$expected" ]]; then + echo "Release name is not as expected. expected: $expected, actual: $actual" + exit 1 + else + echo "Release name is expected! expected: $expected, actual: $actual" + fi + - name: Upload asset files to release + run: | + while read -r asset_path; do + if [[ "${asset_path}" == "" ]]; then continue; fi + # is it a wildcard? + # shellcheck disable=SC2086 + if [[ "$asset_path" == *\** || "$asset_path" == *\?* ]]; then + for file in ${asset_path}; do + gh release upload ${{ inputs.tag }} "${file}" + done + continue + fi + # is it a file? + gh release upload ${{ inputs.tag }} "${asset_path}" + done <<< "${{ inputs.release-asset-path }}" + if: ${{ inputs.release-upload }} + + # Upload to NuGet + - name: Upload to NuGet (DryRun=${{ inputs.dry-run }}) + if: ${{ inputs.nuget-push }} + run: | + while read -r nuget_path; do + if [[ "$nuget_path" == "" ]]; then continue; fi + # shellcheck disable=SC2086 + if ! ls -l ${nuget_path} >/dev/null 2>&1;then + echo "skipping nuget push, $nuget_path not found." + continue + fi + + if [[ "${{ inputs.dry-run }}" == "true" ]]; then + echo "(dry run) dotnet nuget push \"${nuget_path}\" --skip-duplicate -s https://api.nuget.org/v3/index.json -k \"${{ env.NUGET_KEY }}\"" + else + dotnet nuget push "${nuget_path}" --skip-duplicate -s https://api.nuget.org/v3/index.json -k "${{ env.NUGET_KEY }}" + fi + done <<< "${{ inputs.nuget-path }}" + + # Clean up + - name: Clean up. Wait 60s and delete release if dry-run or failure. (dry-run=${{ inputs.dry-run }}}) + if: ${{ inputs.dry-run || failure() }} + run: | + if gh release list | grep Draft | grep ${{ inputs.tag }}; then + sleep 60 + gh release delete ${{ inputs.tag }} --yes --cleanup-tag + fi diff --git a/.github/workflows/_update-packagejson.yaml b/.github/workflows/_update-packagejson.yaml new file mode 100644 index 000000000..c461c9bed --- /dev/null +++ b/.github/workflows/_update-packagejson.yaml @@ -0,0 +1,168 @@ +name: (R) Update package.json + +on: + workflow_call: + inputs: + file-path: + description: "package.json path to update. You can input multiline paths. Supported files are `package.json`, `plugin.cfg` and `Directory.Build.props`" + required: true + type: string + tag: + description: "git tag you want create. (sample v1.0.0)" + required: true + type: string + dry-run: + description: "true to simularate commit but not push change." + required: true + type: boolean + push-tag: + description: "true = push tag. false = no push tag." + required: false + type: boolean + default: true + outputs: + sha: + description: "Git commit sha after package.json has changed." + value: ${{ jobs.update-packagejson.outputs.sha }} + branch-name: + description: Git branch name created. + value: ${{ jobs.update-packagejson.outputs.branch-name }} + is-branch-created: + description: Indicate is Git branch created or not. + value: ${{ jobs.update-packagejson.outputs.is-branch-created }} + normalized_tag: + description: Normalized tag with out v prefix. + value: ${{ jobs.validate.outputs.normalized_tag }} + +jobs: + validate: + runs-on: ubuntu-latest + timeout-minutes: 5 + outputs: + tag: ${{ steps.trim.outputs.tag }} + normalized_tag: ${{ steps.trim.outputs.normalized_tag }} + steps: + - name: tag must begin with v + run: | + input_tag="${{ inputs.tag }}" + if [[ "$input_tag" != v* ]]; then + echo "Tag must begin with v. Current tag: $input_tag" + exit 1 + fi + - name: Set version without "v" prefix + id: trim + run: | + input_tag="${{ inputs.tag }}" + normalized_tag="${input_tag:1}" + echo "normalized_tag=$normalized_tag" | tee -a "$GITHUB_OUTPUT" + echo "tag=${{ inputs.tag }}" | tee -a "$GITHUB_OUTPUT" + + update-packagejson: + needs: [validate] + runs-on: ubuntu-latest + timeout-minutes: 5 + outputs: + sha: ${{ steps.commit.outputs.sha }} + branch-name: ${{ steps.configure.outputs.branch-name }} + is-branch-created: ${{ steps.commit.outputs.is-branch-created }} + steps: + - name: Configure Output variables + id: configure + run: | + echo "branch-name=test-release/${{ inputs.tag }}" | tee -a "$GITHUB_OUTPUT" + + - uses: actions/checkout@v4 + + # package.json + # "version": 1.2.3 -> "version": 2.0.0 + # plugin.cfg + # version="1.2.3" -> version="2.0.0" + # + # TIPS: `grep -v "^$"` is used to remove empty line. + - name: Update files to version ${{ needs.validate.outputs.normalized_tag }} + run: | + expected="${{ needs.validate.outputs.normalized_tag }}" + while read -r file_path; do + if [[ "$file_path" == "" ]]; then continue; fi + + echo "Start $file_path" + file_name=$(basename "$file_path") + + echo "::group::Before" + cat "$file_path" + echo "::endgroup::" + + echo "::group::Updating" + if [[ "${file_name}" == "package.json" ]]; then + # Unity `"version": "VersionString",` + sed -i -e "s/\(\"version\":\) \"\(.*\)\",/\1 \"${{ needs.validate.outputs.normalized_tag }}\",/" "${file_path}" + elif [[ "${file_name}" == "Directory.Build.props" ]]; then + # .NET `VersionString` + sed -i -e 's|.*|${{ needs.validate.outputs.normalized_tag }}|g' "${file_path}" + else + echo "Unknown file name ${file_name} is specified." + exit 1 + fi + echo "::endgroup::" + + echo "::group::After" + cat "$file_path" + echo "::endgroup::" + + echo "::group::Validate Change" + if [[ "${file_name}" == "package.json" ]]; then + actual=$(grep "version" "$file_path" | cut -d ':' -f 2 | tr -d ',' | tr -d '"' | tr -d ' ') + elif [[ "${file_name}" == "Directory.Build.props" ]]; then + # -P is for perl regex, only available in GNU grep + actual=$(grep -oP '\K.*(?=)' "$file_path") + else + echo "Validation for ${file_name} is not implemented." + exit 1 + fi + + if [[ "$actual" != "$expected" ]]; then + echo "Failed. Path: $file_path, Expected: $expected, Actual: $actual" + exit 1 + else + echo "Success. Path: $file_path, Expected: $expected, Actual: $actual" + fi + echo "::endgroup::" + done <<< "${{ inputs.file-path }}" + + - name: Check update on git + id: check_update + run: git diff --exit-code || echo "changed=1" | tee -a "$GITHUB_OUTPUT" + + - name: Commit files (updated? = ${{ steps.check_update.outputs.changed == '1' }}) + id: commit + run: | + if [[ "${{ steps.check_update.outputs.changed }}" == "1" ]]; then + git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + git commit -m "feat: Update package.json to ${{ needs.validate.outputs.normalized_tag }}" -m "Commit by [GitHub Actions](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})" -a + echo "sha=$(git rev-parse HEAD)" | tee -a "$GITHUB_OUTPUT" + echo "is-branch-created=${{ inputs.dry-run }}" | tee -a "$GITHUB_OUTPUT" + else + echo "sha=" | tee -a "$GITHUB_OUTPUT" + echo "is-branch-created=false" | tee -a "$GITHUB_OUTPUT" + fi + + - name: Create Tag + if: ${{ steps.check_update.outputs.changed == '1' && inputs.push-tag }} + run: git tag ${{ inputs.tag }} + + - name: Push changes + if: ${{ !inputs.dry-run && steps.check_update.outputs.changed == '1' }} + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} # auto generated token + tags: ${{ inputs.push-tag }} + + - name: Push changes (dry-run) + if: ${{ inputs.dry-run && steps.check_update.outputs.changed == '1' }} + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} # auto generated token + branch: "refs/heads/${{ steps.configure.outputs.branch-name }}" + tags: false + force: true diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml new file mode 100644 index 000000000..444bdae1a --- /dev/null +++ b/.github/workflows/build-release.yml @@ -0,0 +1,75 @@ +name: Run release build and publish to NuGet + +on: + workflow_dispatch: + inputs: + tag: + description: "tag: git tag you want create. (sample v1.0.0)" + required: true + dry-run: + description: "dry-run: false = create release/nuget. true = never create release/nuget." + required: true + default: false + type: boolean + +permissions: + actions: write + contents: write + +jobs: + # for unity. need update package.json from tag + update-packagejson: + uses: ./.github/workflows/_update-packagejson.yaml + with: + file-path: | + ./src/MessagePack.UnityClient/Assets/Scripts/MessagePack/package.json + tag: ${{ inputs.tag }} + dry-run: ${{ inputs.dry-run }} + push-tag: true + + # for dotnet. Build nuget package + build-dotnet: + needs: [update-packagejson] + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - run: echo ${{ needs.update-packagejson.outputs.sha }} + - uses: actions/checkout@v4 + with: + ref: ${{ needs.update-packagejson.outputs.sha }} + fetch-depth: 0 + - uses: ./.github/actions/setup-dotnet + # pack nuget + - run: dotnet build -c Release -p:Version=${{ needs.update-packagejson.outputs.normalized_tag }} + - run: dotnet test -c Release --no-build + - run: dotnet pack -c Release -p:Version=${{ needs.update-packagejson.outputs.normalized_tag }} -o ./publish + - name: upload artifacts + uses: actions/upload-artifact@v4 # must sync with actions/download-artifact@v4 in create-release + with: + name: nuget + path: ./publish/ + if-no-files-found: 'error' # default 'warn' + retention-days: 1 # expire in 1 day + + # create release and upload nuget + create-release: + needs: [update-packagejson, build-dotnet] + uses: ./.github/workflows/_create-release.yaml + with: + commit-id: ${{ needs.update-packagejson.outputs.sha }} + dry-run: ${{ inputs.dry-run }} + nuget-push: true + release-upload: true + release-asset-path: | + ./nuget/*.nupkg + ./nuget/*.snupkg + tag: ${{ inputs.tag }} + secrets: inherit + + # delete dry-run created git branch + cleanup: + if: ${{ needs.update-packagejson.outputs.is-branch-created == 'true' }} + needs: [update-packagejson, create-release] + uses: ./.github/workflows/_clean-packagejson-branch.yaml + with: + branch: ${{ needs.update-packagejson.outputs.branch-name }} diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml new file mode 100644 index 000000000..578c78cd3 --- /dev/null +++ b/.github/workflows/dotnet.yml @@ -0,0 +1,24 @@ +name: Run .NET Build and Test + +on: + workflow_dispatch: + push: + branches: + - master + - develop + pull_request: + branches: + - master + - develop + +jobs: + build-dotnet: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # avoid shallow clone so nbgv can do its work. + - uses: ./.github/actions/setup-dotnet + - run: dotnet build -c Release -t:build,pack + - run: dotnet test -c Release --no-build diff --git a/.github/workflows/unity.yml b/.github/workflows/unity.yml index 2bf7bcba2..332ebb593 100644 --- a/.github/workflows/unity.yml +++ b/.github/workflows/unity.yml @@ -1,4 +1,4 @@ -name: unity +name: Run Unity IL2CPP UnitTest on: workflow_dispatch: @@ -11,38 +11,54 @@ on: - master - develop +env: + BUILD_CONFIG: Debug + jobs: unity: + if: ${{ ((github.event_name == 'push' && github.repository_owner == 'MessagePack-CSharp') || startsWith(github.event.pull_request.head.label, 'MessagePack-CSharp:')) && github.triggering_actor != 'dependabot[bot]' }} runs-on: ubuntu-latest timeout-minutes: 15 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 # avoid shallow clone so nbgv can do its work. - - uses: actions/setup-dotnet@v3 - with: - global-json-file: global.json - - name: copy assets - run: src/MessagePack.UnityClient/copy_assets.sh - - uses: actions/cache@v3 + - uses: ./.github/actions/setup-dotnet + - run: dotnet build -c ${{ env.BUILD_CONFIG }} # require dotnet build (Debug) before Unity build. + + - name: restore and run local tool for NuGetForUnity + working-directory: ./src/MessagePack.UnityClient + run: | + dotnet tool restore + dotnet nugetforunity restore + + # Run UnitTest + - uses: actions/cache@v4 with: path: src/MessagePack.UnityClient/Library - key: MessagePack-ubuntu - - name: build - uses: game-ci/unity-builder@v2 + key: Library-MessagePack-StandaloneLinux64 + restore-keys: | + Library-MessagePack- + Library- + - name: Build project + uses: game-ci/unity-builder@v4 env: - UNITY_LICENSE: ${{ secrets.UNITY_LICENSE_2021 }} + UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }} + UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} + UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} with: projectPath: src/MessagePack.UnityClient - unityVersion: 2021.3.11f1 + unityVersion: 2022.3.12f1 targetPlatform: StandaloneLinux64 - buildMethod: PackageExporter.Export - customParameters: /headless /ScriptBackend mono + buildMethod: UnitTestBuilder.BuildUnitTest versioning: None - - uses: Cysharp/Actions/.github/actions/check-metas@main # check meta files + customParameters: "/headless /ScriptBackend IL2CPP" + - name: Check UnitTest file is generated + run: ls -lR ./src/MessagePack.UnityClient/bin/UnitTest + - name: Execute UnitTest + run: ./src/MessagePack.UnityClient/bin/UnitTest/StandaloneLinux64_IL2CPP/test + + # check meta files + - uses: ./.github/actions/check-metas with: directory: src/MessagePack.UnityClient - - uses: actions/upload-artifact@v3 - with: - name: MessagePack.unitypackage - path: bin/*.unitypackage diff --git a/.gitignore b/.gitignore index 7e74065d1..6ef3a7ca5 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,9 @@ bld/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ +# Jetbrains Rider cache directory +.idea/ + # Visual Studio 2017 auto generated files Generated\ Files/ @@ -65,7 +68,6 @@ StyleCopReport.xml *_p.c *_h.h *.ilk -*.meta *.obj *.iobj *.pch @@ -353,12 +355,19 @@ MigrationBackup/ # mac-created file to track user view preferences for a directory .DS_Store +# Analysis results +*.sarif + # Unity src/MessagePack.UnityClient/bin/* src/MessagePack.UnityClient/Library/* src/MessagePack.UnityClient/obj/* src/MessagePack.UnityClient/Temp/* +src/MessagePack.UnityClient/UserSettings/* +src/MessagePack.UnityClient/Assets/Packages/ # BenchmarkDotNet results BenchmarkDotNet.Artifacts/ + +src/MessagePack.UnityClient/.vsconfig diff --git a/.vscode/extensions.json b/.vscode/extensions.json index ca3a2aa9d..acaf02131 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -7,6 +7,7 @@ "ms-dotnettools.csharp", "k--kato.docomment", "editorconfig.editorconfig", + "esbenp.prettier-vscode", "pflannery.vscode-versionlens", "davidanson.vscode-markdownlint", "dotjoshjohnson.xml", diff --git a/.vscode/settings.json b/.vscode/settings.json index 3ae1371c6..7743c118d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,17 @@ "files.insertFinalNewline": true, "files.trimFinalNewlines": true, "omnisharp.enableEditorConfigSupport": true, - "omnisharp.enableImportCompletion": true, - "omnisharp.enableRoslynAnalyzers": true + "omnisharp.enableRoslynAnalyzers": true, + "dotnet.completion.showCompletionItemsFromUnimportedNamespaces": true, + "editor.formatOnSave": true, + "[xml]": { + "editor.wordWrap": "off" + }, + // Use Prettier as the default formatter for Azure Pipelines files. + // Needs to be explicitly configured: https://github.com/Microsoft/azure-pipelines-vscode#document-formatting + "[azure-pipelines]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": false // enable this when they conform + }, + "dotnet.defaultSolution": "MessagePack.sln" } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 842ccfc2c..0883dcdd5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ ## Dependencies -* [Visual Studio 2019](https://visualstudio.microsoft.com/) +* [Visual Studio 2022](https://visualstudio.microsoft.com/) * [Unity Editor](https://unity3d.com/unity/editor) (optional) * .NET Core SDK and runtimes (run `init` to install) @@ -14,7 +14,7 @@ To get VS to find the toolsets when launched from the Start Menu, run `init -Ins ## How to Build -Open `MessagePack.sln` on Visual Studio 2019. +Open `MessagePack.sln` on Visual Studio 2022. Alternatively you may build from the command line using `msbuild.exe` or: @@ -22,15 +22,36 @@ Alternatively you may build from the command line using `msbuild.exe` or: ## Unity -Unity Project requires several dependency DLL's. At first, run `copy_assets.bat` under `src\MessagePack.UnityClient`. -Then open that directory in the Unity Editor. +See the ReadMe for the target directory `src\MessagePack.UnityClient` for information on building and managing with Unity. Unity's CI is managed in `unity.yml` in GitHub Actions. -## Where to find our CI feed +## How to Publish Package -Once a change is in a shipping branch (e.g. `v1.8`, `v2.0`, `master`), our CI will build it and push the built package -to [our CI feed](https://dev.azure.com/ils0086/MessagePack-CSharp/_packaging?_a=feed&feed=MessagePack-CI). To depend on -one of the packages that are on our CI feed (but not yet on nuget.org) you can add this to your nuget.config file: +Package publishing is triggered via GitHub Actions using workflow_dispatch. Follow these steps: -```xml - -``` +1. Select Actions -> "Run release build and publish to NuGet" +2. Enter a version tag (e.g., `v3.0.1`) +3. Click "Run workflow" + +![image](https://github.com/user-attachments/assets/74886c88-f6d1-4108-8ce1-02d3d1b31f1f) + +The workflow will: +- Update the version in [MessagePack.UnityClient/Assets/Scripts/MessagePack/package.json](https://github.com/MessagePack-CSharp/MessagePack-CSharp/blob/master/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/package.json) +- Commit and push the change +- Build the .NET library +- Publish to [NuGet/MessagePack](https://www.nuget.org/packages/MessagePack) +- Create a draft GitHub release + +After CI completion, edit the release draft to add relevant release notes and announcements. + +### Secret + +The following secrets are managed at the organization level: + +* `UNITY_EMAIL` +* `UNITY_LICENSE` +* `UNITY_PASSWORD` +* `NUGET_KEY` + +The `UNITY_*` secrets are personal license keys required for Unity builds. + +`NUGET_KEY` is a key required for releasing nupkg files, and since it has a 365-day expiration period, the key needs to be regenerated when it expires. \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index 2508970e4..2c647f7e6 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,3 +1,4 @@ + Debug @@ -5,7 +6,6 @@ $(RepoRootPath)obj\$([MSBuild]::MakeRelative($(RepoRootPath), $(MSBuildProjectDirectory)))\ $(RepoRootPath)bin\$(MSBuildProjectName)\ $(RepoRootPath)bin\Packages\$(Configuration)\ - 10 latest true true @@ -18,13 +18,10 @@ false - - $(MSBuildThisFileDirectory) - embedded - https://github.com/neuecc/MessagePack-CSharp + https://github.com/MessagePack-CSharp/MessagePack-CSharp neuecc,aarnott © Yoshifumi Kawai and contributors. All rights reserved. MIT @@ -43,28 +40,4 @@ - - - $(PackageProjectUrl)/releases/tag/v$(Version) - - - - - false - true - - - - - false - false - false - false - diff --git a/Directory.Build.targets b/Directory.Build.targets index ea7b6e6f8..ecd71a31b 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -1,9 +1,9 @@ + - - false + 12 + 16.9 - diff --git a/Directory.Packages.props b/Directory.Packages.props index cdeb77b49..6fe591f69 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,75 +1,78 @@ + true true - 0.13.5 - 3.11.0 - 4.4.0 + 0.14.0 + + 4.8.0 + 4.3.0 + 1.1.2 - + - - - - - - + + + + - - + + - - - + + + - + - + - + + - + - + + - - - + - - - - + + + + + - - - + + + + + + + + - - - - - - - - + + - + + + - \ No newline at end of file + diff --git a/MessagePack.sln b/MessagePack.sln index 2b743e8e3..77756e3c0 100644 --- a/MessagePack.sln +++ b/MessagePack.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 -VisualStudioVersion = 17.5.33201.384 +VisualStudioVersion = 17.4.33103.184 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{86309CF6-0054-4CE3-BFD3-CA0AA7DB17BC}" ProjectSection(SolutionItems) = preProject @@ -12,6 +12,10 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessagePack", "src\MessagePack\MessagePack.csproj", "{7ABB33EE-A2F1-492B-8DAF-5DF89F0F0B79}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{19FE674A-AC94-4E7E-B24C-2285D1D04CDE}" + ProjectSection(SolutionItems) = preProject + tests\SourceGeneratorConsumer.props = tests\SourceGeneratorConsumer.props + tests\SourceGeneratorConsumer.targets = tests\SourceGeneratorConsumer.targets + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessagePack.Tests", "tests\MessagePack.Tests\MessagePack.Tests.csproj", "{9E1A55CA-711D-4F58-A332-735960E3434C}" EndProject @@ -36,8 +40,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessagePack.ReactivePropert EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessagePack.ImmutableCollection", "src\MessagePack.ImmutableCollection\MessagePack.ImmutableCollection.csproj", "{E066F547-7261-4561-AEFC-E64DBFD874F8}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessagePackAnalyzer", "src\MessagePackAnalyzer\MessagePackAnalyzer.csproj", "{2F9A6E0C-DE95-4460-96B7-EB72BBEAEE9E}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PerfNetFramework", "sandbox\PerfNetFramework\PerfNetFramework.csproj", "{014A3DCE-50A6-4774-A4C1-C66EEAB67133}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessagePack.AspNetCoreMvcFormatter", "src\MessagePack.AspNetCoreMvcFormatter\MessagePack.AspNetCoreMvcFormatter.csproj", "{17831017-C29C-4A48-B159-849BCE5079FB}" @@ -49,14 +51,11 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{301F812B-8AEE-4DC2-8009-4510F02294AD}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig - azure-pipelines.yml = azure-pipelines.yml - azure-pipelines\build.yml = azure-pipelines\build.yml Directory.Build.props = Directory.Build.props Directory.Packages.props = Directory.Packages.props global.json = global.json nuget.config = nuget.config stylecop.json = stylecop.json - version.json = version.json EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessagePack.AspNetCoreMvcFormatter.Tests", "tests\MessagePack.AspNetCoreMvcFormatter.Tests\MessagePack.AspNetCoreMvcFormatter.Tests.csproj", "{79C2B2CB-872A-4BA9-82DC-60F6DD77F940}" @@ -76,15 +75,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessagePack.Internal", "san EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessagePack.Internal.Tests", "tests\MessagePack.Internal.Tests\MessagePack.Internal.Tests.csproj", "{8D9FD130-7905-47D8-A25C-7FDEE28EA0E8}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessagePack.GeneratorCore", "src\MessagePack.GeneratorCore\MessagePack.GeneratorCore.csproj", "{9962132D-A271-4E68-ACC1-18FA93462552}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessagePack.Generator", "src\MessagePack.Generator\MessagePack.Generator.csproj", "{32C91908-5CAD-4C95-B240-ACBBACAC9476}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessagePack.MSBuild.Tasks", "src\MessagePack.MSBuild.Tasks\MessagePack.MSBuild.Tasks.csproj", "{8DB135F5-A6FE-44E4-9853-7B48ED21F21B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessagePackAnalyzer.Tests", "tests\MessagePackAnalyzer.Tests\MessagePackAnalyzer.Tests.csproj", "{7E5FB4B9-A0F5-4B10-A1F3-03AC0BC8265A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessagePack.SourceGenerator", "src\MessagePack.SourceGenerator\MessagePack.SourceGenerator.csproj", "{32C91908-5CAD-4C95-B240-ACBBACAC9476}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessagePack.Generator.Tests", "tests\MessagePack.Generator.Tests\MessagePack.Generator.Tests.csproj", "{6AC51E68-4681-463A-B4B6-BD53517244B2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessagePack.SourceGenerator.Tests", "tests\MessagePack.SourceGenerator.Tests\MessagePack.SourceGenerator.Tests.csproj", "{6AC51E68-4681-463A-B4B6-BD53517244B2}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExperimentalBenchmark", "benchmark\ExperimentalBenchmark\ExperimentalBenchmark.csproj", "{4C9BB260-62D8-49CD-9F9C-9AA6A8BFC637}" EndProject @@ -94,6 +87,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessagePack.Experimental.Te EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessagePack.GeneratedCode.Tests", "tests\MessagePack.GeneratedCode.Tests\MessagePack.GeneratedCode.Tests.csproj", "{D4CE7347-CEBE-46E5-BD12-1319573B6C5E}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessagePack.SourceGenerator.ExecutionTests", "tests\MessagePack.SourceGenerator.ExecutionTests\MessagePack.SourceGenerator.ExecutionTests.csproj", "{7908D954-15D4-4D67-B49A-4484809DA2C4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessagePack.SourceGenerator.MapModeExecutionTests", "tests\MessagePack.SourceGenerator.MapModeExecutionTests\MessagePack.SourceGenerator.MapModeExecutionTests.csproj", "{EDBA7DDC-69AF-4D5B-A8F6-3B508F8CC0FC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessagePack.Analyzers.CodeFixes", "src\MessagePack.Analyzers.CodeFixes\MessagePack.Analyzers.CodeFixes.csproj", "{7A6CB600-2393-468F-9952-84EC624D57BD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessagePack.Analyzers", "src\MessagePack.Analyzers\MessagePack.Analyzers.csproj", "{EB77463C-9D06-4AAE-84F0-470988D30DA0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CollectionsMarshalBenchmark", "benchmark\CollectionsMarshalBenchmark\CollectionsMarshalBenchmark.csproj", "{9A31C44C-9C51-4D41-B8E5-2864245F877E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -132,10 +135,6 @@ Global {E066F547-7261-4561-AEFC-E64DBFD874F8}.Debug|Any CPU.Build.0 = Debug|Any CPU {E066F547-7261-4561-AEFC-E64DBFD874F8}.Release|Any CPU.ActiveCfg = Release|Any CPU {E066F547-7261-4561-AEFC-E64DBFD874F8}.Release|Any CPU.Build.0 = Release|Any CPU - {2F9A6E0C-DE95-4460-96B7-EB72BBEAEE9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2F9A6E0C-DE95-4460-96B7-EB72BBEAEE9E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2F9A6E0C-DE95-4460-96B7-EB72BBEAEE9E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2F9A6E0C-DE95-4460-96B7-EB72BBEAEE9E}.Release|Any CPU.Build.0 = Release|Any CPU {014A3DCE-50A6-4774-A4C1-C66EEAB67133}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {014A3DCE-50A6-4774-A4C1-C66EEAB67133}.Debug|Any CPU.Build.0 = Debug|Any CPU {014A3DCE-50A6-4774-A4C1-C66EEAB67133}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -172,22 +171,10 @@ Global {8D9FD130-7905-47D8-A25C-7FDEE28EA0E8}.Debug|Any CPU.Build.0 = Debug|Any CPU {8D9FD130-7905-47D8-A25C-7FDEE28EA0E8}.Release|Any CPU.ActiveCfg = Release|Any CPU {8D9FD130-7905-47D8-A25C-7FDEE28EA0E8}.Release|Any CPU.Build.0 = Release|Any CPU - {9962132D-A271-4E68-ACC1-18FA93462552}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9962132D-A271-4E68-ACC1-18FA93462552}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9962132D-A271-4E68-ACC1-18FA93462552}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9962132D-A271-4E68-ACC1-18FA93462552}.Release|Any CPU.Build.0 = Release|Any CPU {32C91908-5CAD-4C95-B240-ACBBACAC9476}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {32C91908-5CAD-4C95-B240-ACBBACAC9476}.Debug|Any CPU.Build.0 = Debug|Any CPU {32C91908-5CAD-4C95-B240-ACBBACAC9476}.Release|Any CPU.ActiveCfg = Release|Any CPU {32C91908-5CAD-4C95-B240-ACBBACAC9476}.Release|Any CPU.Build.0 = Release|Any CPU - {8DB135F5-A6FE-44E4-9853-7B48ED21F21B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8DB135F5-A6FE-44E4-9853-7B48ED21F21B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8DB135F5-A6FE-44E4-9853-7B48ED21F21B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8DB135F5-A6FE-44E4-9853-7B48ED21F21B}.Release|Any CPU.Build.0 = Release|Any CPU - {7E5FB4B9-A0F5-4B10-A1F3-03AC0BC8265A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7E5FB4B9-A0F5-4B10-A1F3-03AC0BC8265A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7E5FB4B9-A0F5-4B10-A1F3-03AC0BC8265A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7E5FB4B9-A0F5-4B10-A1F3-03AC0BC8265A}.Release|Any CPU.Build.0 = Release|Any CPU {6AC51E68-4681-463A-B4B6-BD53517244B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6AC51E68-4681-463A-B4B6-BD53517244B2}.Debug|Any CPU.Build.0 = Debug|Any CPU {6AC51E68-4681-463A-B4B6-BD53517244B2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -208,6 +195,26 @@ Global {D4CE7347-CEBE-46E5-BD12-1319573B6C5E}.Debug|Any CPU.Build.0 = Debug|Any CPU {D4CE7347-CEBE-46E5-BD12-1319573B6C5E}.Release|Any CPU.ActiveCfg = Release|Any CPU {D4CE7347-CEBE-46E5-BD12-1319573B6C5E}.Release|Any CPU.Build.0 = Release|Any CPU + {7908D954-15D4-4D67-B49A-4484809DA2C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7908D954-15D4-4D67-B49A-4484809DA2C4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7908D954-15D4-4D67-B49A-4484809DA2C4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7908D954-15D4-4D67-B49A-4484809DA2C4}.Release|Any CPU.Build.0 = Release|Any CPU + {EDBA7DDC-69AF-4D5B-A8F6-3B508F8CC0FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EDBA7DDC-69AF-4D5B-A8F6-3B508F8CC0FC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EDBA7DDC-69AF-4D5B-A8F6-3B508F8CC0FC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EDBA7DDC-69AF-4D5B-A8F6-3B508F8CC0FC}.Release|Any CPU.Build.0 = Release|Any CPU + {7A6CB600-2393-468F-9952-84EC624D57BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7A6CB600-2393-468F-9952-84EC624D57BD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7A6CB600-2393-468F-9952-84EC624D57BD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7A6CB600-2393-468F-9952-84EC624D57BD}.Release|Any CPU.Build.0 = Release|Any CPU + {EB77463C-9D06-4AAE-84F0-470988D30DA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EB77463C-9D06-4AAE-84F0-470988D30DA0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EB77463C-9D06-4AAE-84F0-470988D30DA0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EB77463C-9D06-4AAE-84F0-470988D30DA0}.Release|Any CPU.Build.0 = Release|Any CPU + {9A31C44C-9C51-4D41-B8E5-2864245F877E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9A31C44C-9C51-4D41-B8E5-2864245F877E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9A31C44C-9C51-4D41-B8E5-2864245F877E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9A31C44C-9C51-4D41-B8E5-2864245F877E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -221,7 +228,6 @@ Global {C01E1407-7FEC-4C1D-B0B4-74D95A317AA6} = {86309CF6-0054-4CE3-BFD3-CA0AA7DB17BC} {166A16C0-B89F-41AF-956A-235C6CA62C25} = {86309CF6-0054-4CE3-BFD3-CA0AA7DB17BC} {E066F547-7261-4561-AEFC-E64DBFD874F8} = {86309CF6-0054-4CE3-BFD3-CA0AA7DB17BC} - {2F9A6E0C-DE95-4460-96B7-EB72BBEAEE9E} = {86309CF6-0054-4CE3-BFD3-CA0AA7DB17BC} {014A3DCE-50A6-4774-A4C1-C66EEAB67133} = {BF4C4202-5015-4FBD-80E6-D0F36A06F700} {17831017-C29C-4A48-B159-849BCE5079FB} = {86309CF6-0054-4CE3-BFD3-CA0AA7DB17BC} {814F94D6-1413-4ACB-B1B5-A3488CAA1E6B} = {BF4C4202-5015-4FBD-80E6-D0F36A06F700} @@ -231,15 +237,17 @@ Global {4142EA80-FEF4-44A5-8553-1AE84BEBAFED} = {51A614B0-E583-4DD2-AC7D-6A65634582E0} {C100FBA6-4164-4D6A-A532-5984D2B8DCB0} = {BF4C4202-5015-4FBD-80E6-D0F36A06F700} {8D9FD130-7905-47D8-A25C-7FDEE28EA0E8} = {19FE674A-AC94-4E7E-B24C-2285D1D04CDE} - {9962132D-A271-4E68-ACC1-18FA93462552} = {86309CF6-0054-4CE3-BFD3-CA0AA7DB17BC} {32C91908-5CAD-4C95-B240-ACBBACAC9476} = {86309CF6-0054-4CE3-BFD3-CA0AA7DB17BC} - {8DB135F5-A6FE-44E4-9853-7B48ED21F21B} = {86309CF6-0054-4CE3-BFD3-CA0AA7DB17BC} - {7E5FB4B9-A0F5-4B10-A1F3-03AC0BC8265A} = {19FE674A-AC94-4E7E-B24C-2285D1D04CDE} {6AC51E68-4681-463A-B4B6-BD53517244B2} = {19FE674A-AC94-4E7E-B24C-2285D1D04CDE} {4C9BB260-62D8-49CD-9F9C-9AA6A8BFC637} = {51A614B0-E583-4DD2-AC7D-6A65634582E0} {AC2503A7-736D-4AE6-9355-CF35D9DF6139} = {86309CF6-0054-4CE3-BFD3-CA0AA7DB17BC} {8AB40D1C-1134-4D77-B39A-19AEDC729450} = {19FE674A-AC94-4E7E-B24C-2285D1D04CDE} {D4CE7347-CEBE-46E5-BD12-1319573B6C5E} = {19FE674A-AC94-4E7E-B24C-2285D1D04CDE} + {7908D954-15D4-4D67-B49A-4484809DA2C4} = {19FE674A-AC94-4E7E-B24C-2285D1D04CDE} + {EDBA7DDC-69AF-4D5B-A8F6-3B508F8CC0FC} = {19FE674A-AC94-4E7E-B24C-2285D1D04CDE} + {7A6CB600-2393-468F-9952-84EC624D57BD} = {86309CF6-0054-4CE3-BFD3-CA0AA7DB17BC} + {EB77463C-9D06-4AAE-84F0-470988D30DA0} = {86309CF6-0054-4CE3-BFD3-CA0AA7DB17BC} + {9A31C44C-9C51-4D41-B8E5-2864245F877E} = {51A614B0-E583-4DD2-AC7D-6A65634582E0} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B3911209-2DBF-47F8-98F6-BBC0EDFE63DE} diff --git a/README.md b/README.md index 294afa514..30cea70f9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# MessagePack for C# (.NET Framework, .NET 6, Unity, Xamarin) +# MessagePack for C# (.NET Framework, .NET 8, Unity, Xamarin) [![NuGet](https://img.shields.io/nuget/v/MessagePack.svg)](https://www.nuget.org/packages/messagepack) [![NuGet](https://img.shields.io/nuget/vpre/MessagePack.svg)](https://www.nuget.org/packages/messagepack) @@ -57,19 +57,20 @@ MessagePack has a compact binary size and a full set of general purpose expressi - [IgnoreFormatter](#ignoreformatter) - [Reserved Extension Types](#reserved-extension-types) - [Unity support](#unity-support) -- [AOT Code Generation (support for Unity/Xamarin)](#aot-code-generation-support-for-unityxamarin) +- [AOT Code Generation (support for Unity/Xamarin)](#aot) - [RPC](#rpc) - [MagicOnion](#magiconion) - [StreamJsonRpc](#streamjsonrpc) - [How to build](#how-to-build) - [Author Info](#author-info) +- [Code of Conduct & .NET Foundation notice](#coc) ## Installation This library is distributed via NuGet. Special [Unity support](#unity) is available, too. -We target .NET Standard 2.0 with special optimizations for .NET 6+, making it compatible with most reasonably recent .NET runtimes such as Core 2.0 and later, Framework 4.6.1 and later, Mono 5.4 and later and Unity 2018.3 and later. -The library code is pure C# (with Just-In-Time IL code generation on some platforms). +We target .NET Standard 2.0 with special optimizations for .NET 8+ and .NET Framework. +The library code is pure C# (with Just-In-Time IL code generation on some platforms or AOT safe source generators). ### NuGet packages @@ -79,12 +80,6 @@ To install with NuGet, just install the `MessagePack` package: Install-Package MessagePack ``` -Install the optional C# [analyzers](doc/analyzers/index.md) package to get warnings about coding mistakes and automatic fix suggestions to save you time: - -```ps1 -Install-Package MessagePackAnalyzer -``` - There are also a range of official and third party Extension Packages available (learn more in our [extensions section](#extensions)): ```ps1 @@ -95,11 +90,12 @@ Install-Package MessagePack.AspNetCoreMvcFormatter ### Unity -For Unity projects, the [Releases](https://github.com/MessagePack-CSharp/MessagePack-CSharp/releases) page provides downloadable `.unitypackage` files. When using in Unity IL2CPP or Xamarin AOT environments, please carefully read the [pre-code generation section](#aot). +For Unity projects, please read the [Unity Support](#unity-support) section to install. -### Migration notes from v1.x +### Migration notes from prior versions -If you were using MessagePack for C# v1.x, check out the ["How to update to our new v2.x version"](doc/migration.md) document. +Migrating from a prior major version of MessagePack to the latest? +Check out [these instructions](doc/migration.md). ## Quick Start @@ -161,22 +157,23 @@ By default, a `MessagePackObject` annotation is required. This can be made optio ## Analyzer -The MessagePackAnalyzer package aids with: +The MessagePackAnalyzer package(includes in default) aids with: -1. Automating definitions for your serializable objects. 1. Produces compiler warnings upon incorrect attribute use, member accessibility, and more. +1. Automating attributing of your serializable classes and members. +1. Optionally improving startup time through [AOT formatter generation](#aot). -![analyzergif](https://cloud.githubusercontent.com/assets/46207/23837445/ce734eae-07cb-11e7-9758-d69f0f095bc1.gif) +The first two of these features is demonstrated below: -If you want to allow a specific custom type (for example, when registering a custom type), put `MessagePackAnalyzer.json` at the project root and change the Build Action to `AdditionalFiles`. - -![image](https://cloud.githubusercontent.com/assets/46207/23837427/8a8d507c-07cb-11e7-9277-5a566eb0bfde.png) +![analyzergif](https://cloud.githubusercontent.com/assets/46207/23837445/ce734eae-07cb-11e7-9758-d69f0f095bc1.gif) -An example `MessagePackAnalyzer.json`: +Two assembly-level attributes exist to help with mixing in your own custom formatters with the automatically generated ones: +- `MessagePackKnownFormatterAttribute` - Identifies classes that implement `IMessagePackFormatter`. +The `T` type argument will _not_ produce an analyzer warning when `T` is used elsewhere in a serializable object. +When using a source generated resolver, the resolver will refer to this formatter for the appropriate type(s). +- `MessagePackAssumedFormattableAttribute` - Identifies types that are assumed to have an `IMessagePackFormatter` *somewhere*, and that will be combined within an `IFormatterResolver` at runtime to ensure the specified type can be serialized. +This attribute will suppress the analyzer warning from using that type although the type does not have a `[MessagePackObject]` attribute on it. -```json -[ "MyNamespace.FooClass", "MyNameSpace.BarStruct" ] -``` ## Built-in supported types @@ -348,7 +345,10 @@ You can use `[DataContract]` annotations instead of `[MessagePackObject]` ones. Then `[DataMember(Order = int)]` will behave the same as `[Key(int)]`, `[DataMember(Name = string)]` the same as `[Key(string)]`, and `[DataMember]` the same as `[Key(nameof(member name)]`. -Using `DataContract`, e.g. in shared libraries, makes your classes/structs independent from MessagePack for C# serialization. However, it is not supported by the analyzers nor in code generation by the `mpc` tool. Also, features like `UnionAttribute`, `MessagePackFormatter`, `SerializationConstructor`, etc can not be used. Due to this, we recommend that you use the specific MessagePack for C# annotations when possible. +Using `DataContract`, e.g. in shared libraries, makes your classes/structs independent from MessagePack for C# serialization. +However, it is not supported by the analyzers nor source generator. +Also, features like `UnionAttribute`, `MessagePackFormatter`, `SerializationConstructor`, etc can not be used. +Due to this, we recommend that you use the specific MessagePack for C# annotations when possible. ## Serializing readonly/immutable object members (SerializationConstructor) @@ -721,7 +721,7 @@ Performance varies depending on the options used. This is a micro benchmark with | JilString | 553.65 ns | NA | 7.62 | 0.0362 | 152 B | | JilStreamReader | 1,408.46 ns | NA | 19.38 | 0.8450 | 3552 B | -`ÌntKey`, `StringKey`, `Typeless_IntKey`, `Typeless_StringKey` are MessagePack for C# options. All MessagePack for C# options achieve zero memory allocations in the deserialization process. `JsonNetString`/`JilString` is deserialized from strings. `JsonNetStreamReader`/`JilStreamReader` is deserialized from UTF-8 byte arrays using `StreamReader`. Deserialization is normally read from Stream. Thus, it will be restored from byte arrays (or Stream) instead of strings. +`IntKey`, `StringKey`, `Typeless_IntKey`, `Typeless_StringKey` are MessagePack for C# options. All MessagePack for C# options achieve zero memory allocations in the deserialization process. `JsonNetString`/`JilString` is deserialized from strings. `JsonNetStreamReader`/`JilStreamReader` is deserialized from UTF-8 byte arrays using `StreamReader`. Deserialization is normally read from Stream. Thus, it will be restored from byte arrays (or Stream) instead of strings. MessagePack for C# `IntKey` is the fastest. `StringKey` is slower than `IntKey` because matching the character string of property names is required. `IntKey` works by reading the array length, then `for (array length) { binary decode }`. `StringKey` works by reading map length, `for (map length) { decode key, lookup key, binary decode }`, so it requires an additional two steps (decoding of keys and lookups of keys). @@ -1529,17 +1529,49 @@ Within the *reserved* ranges, this library defines or implements extensions that ## Unity support -Unity lowest supported version is `2018.3`, API Compatibility Level supports both `.NET 4.x` and `.NET Standard 2.0`. +The minimum supported Unity version will be `2022.3.12f1`, as it is necessary to support IL2CPP via C# Source Generator. + +There are two installation steps required to use it in Unity. Do both, not just one. + +1. Install `MessagePack` from NuGet using [NuGetForUnity](https://github.com/GlitchEnzo/NuGetForUnity) + Open Window from NuGet -> Manage NuGet Packages, Search "MessagePack" and Press Install. + +2. Install `MessagePack.Unity` package by referencing the git URL. + Open Package Manager window and press `Add Package from git URL...`, enter following path + + ``` + https://github.com/MessagePack-CSharp/MessagePack-CSharp.git?path=src/MessagePack.UnityClient/Assets/Scripts/MessagePack + ``` + + MessagePack uses the ..* release tag, so you can specify a version like #v3.0.0. For example: `https://github.com/MessagePack-CSharp/MessagePack-CSharp.git?path=src/MessagePack.UnityClient/Assets/Scripts/MessagePack#v3.0.0` + +In Unity, MessagePackSerializer can serialize `Vector2`, `Vector3`, `Vector4`, `Quaternion`, `Color`, `Bounds`, `Rect`, `AnimationCurve`, `Keyframe`, `Matrix4x4`, `Gradient`, `Color32`, `RectOffset`, `LayerMask`, `Vector2Int`, `Vector3Int`, `RangeInt`, `RectInt`, `BoundsInt` and their nullable, array and list types with the built-in extension `UnityResolver`. + +`MessagePack.Unity` automatically adds `UnityResolver` to the default options Resolver when the application starts with code like this in the unity package to enable this serialization: + +```csharp +[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] +private static void Init() +{ + MessagePackSerializer.DefaultOptions = MessagePackSerializerOptions.Standard.WithResolver(UnityResolver.InstanceWithStandardResolver); +} +``` + +If you want to customize the Resolver or change the DefaultOptions, it would be good to keep this in mind. -You can install the `unitypackage` from the [Releases](https://github.com/MessagePack-CSharp/MessagePack-CSharp/releases) page. -If your build targets .NET Framework 4.x and runs on mono, you can use it as is. -But if your build targets IL2CPP, you can not use `Dynamic***Resolver`, so it is required to use pre-code generation. Please see [pre-code generation section](#aot). +### Share types with .NET -MessagePack for C# includes some additional `System.*.dll` libraries that originally provides in NuGet. They are located under `Plugins`. If other packages use these libraries (e.g. Unity Collections package using `System.Runtime.CompilerServices.Unsafe.dll`), to avoid conflicts, please delete the DLL under `Plugins`. +The `MessagePack.UnityShims` NuGet package is for .NET server-side serialization support to communicate with Unity. It includes shims for Vector3 etc and the Safe/Unsafe serialization extension. -Currently `CompositeResolver.Create` does not work on IL2CPP, so it is recommended to use `StaticCompositeResolver.Instance.Register` instead. +There are several ways to share types between .NET and Unity: -In Unity, MessagePackSerializer can serialize `Vector2`, `Vector3`, `Vector4`, `Quaternion`, `Color`, `Bounds`, `Rect`, `AnimationCurve`, `Keyframe`, `Matrix4x4`, `Gradient`, `Color32`, `RectOffset`, `LayerMask`, `Vector2Int`, `Vector3Int`, `RangeInt`, `RectInt`, `BoundsInt` and their nullable, array and list types with the built-in extension `UnityResolver`. It is included in StandardResolver by default. +* Share using symbolic links +* Place the actual files on the Unity side and reference them as link files in the .NET csproj +* Use UPM local references to reference the .NET project from the Unity side + +While the setup is a bit challenging, the smoothest way to share is using UPM local references. For detailed steps, please refer to the [MagicOnion Sample](https://github.com/Cysharp/MagicOnion/tree/main/samples/ChatApp). + +### UnsafeBlitResolver MessagePack for C# has an additional unsafe extension. `UnsafeBlitResolver` is special resolver for extremely fast but unsafe serialization/deserialization of struct arrays. @@ -1562,133 +1594,58 @@ var options = MessagePackSerializerOptions.Standard.WithResolver(StaticComposite MessagePackSerializer.DefaultOptions = options; ``` -The `MessagePack.UnityShims` NuGet package is for .NET server-side serialization support to communicate with Unity. It includes shims for Vector3 etc and the Safe/Unsafe serialization extension. - -If you want to share a class between Unity and a server, you can use `SharedProject` or `Reference as Link` or a glob reference (with `LinkBase`), etc. Anyway, you need to share at source-code level. This is a sample project structure using a glob reference (recommended). +## AOT Code Generation -- ServerProject(.NET Framework 4.6/.NET/.NET Standard) - - \[``\] - - \[MessagePack\] - - \[MessagePack.UnityShims\] -- UnityProject - - \[Concrete SharedCodes\] - - \[MessagePack\](not dll/NuGet, use MessagePack.Unity.unitypackage's sourcecode) +A source generator is provided in the `MessagePackAnalyzer` package, which is automatically installed when you install `MessagePack` via NuGet. +This will source generate the formatters required for all your `[MessagePackObject]`-annotated data types during compilation for the fastest possible startup and runtime. +An `IFormatterResolver` is also generated that bundles all source generated and user-written formatters together. +The `StandardResolver` includes the `SourceGeneratedFormatterResolver` which discovers and uses your source generated resolver automatically. -## AOT Code Generation (support for Unity/Xamarin) +Therefore, in the usual scenario, it will work with AOT Safe without any special handling. +If you prefer to restrict your resolver to source-generated formatters, you should use the `MessagePack.GeneratedMessagePackResolver`, which is source generated into your project for that purpose. +This type's name and namespace can be customized by applying `[GeneratedMessagePackResolver]` to a `partial class` that you define, at which point that class becomes the resolver for you to use. -By default, MessagePack for C# serializes custom objects by [generating IL](https://msdn.microsoft.com/en-us/library/system.reflection.emit.ilgenerator.aspx) on the fly at runtime to create custom, highly tuned formatters for each type. This code generation has a minor upfront performance cost. -Because strict-AOT environments such as Xamarin and Unity IL2CPP forbid runtime code generation, MessagePack provides a way for you to run a code generator ahead of time as well. +At runtime, if a source generated or hand-written formatter cannot be found for a given `[MessagePackObject]` type, MessagePack will generate the formatters on the fly using [Reflection.Emit](https://learn.microsoft.com/dotnet/api/system.reflection.emit.ilgenerator) to create highly-tuned formatters for each type. +This code generation has a minor upfront performance cost. > Note: When using Unity, dynamic code generation only works when targeting .NET Framework 4.x + mono runtime. For all other Unity targets, AOT is required. -If you want to avoid the upfront dynamic generation cost or you need to run on Xamarin or Unity, you need AOT code generation. `mpc` (MessagePackCompiler) is the code generator of MessagePack for C#. mpc uses [Roslyn](https://github.com/dotnet/roslyn) to analyze source code. - -First of all, mpc requires [.NET 6+ Runtime](https://dotnet.microsoft.com/download). The easiest way to acquire and run mpc is as a dotnet tool. - -``` -dotnet tool install --global MessagePack.Generator -``` - -Installing it as a local tool allows you to include the tools and versions that you use in your source control system. Run these commands in the root of your repo: - -``` -dotnet new tool-manifest -dotnet tool install MessagePack.Generator -``` - -Check in your `.config\dotnet-tools.json` file. On another machine you can "restore" your tool using the `dotnet tool restore` command. - -Once you have the tool installed, simply invoke using `dotnet mpc` within your repo: - -``` -dotnet mpc --help - -Usage: mpc [options...] - -Options: - -i, -input Input path to MSBuild project file or the directory containing Unity source files. (Required) - -o, -output Output file path(.cs) or directory(multiple generate file). (Required) - -c, -conditionalSymbol Conditional compiler symbols, split with ','. (Default: null) - -r, -resolverName Set resolver name. (Default: GeneratedResolver) - -n, -namespace Set namespace root name. (Default: MessagePack) - -m, -useMapMode Force use map mode serialization. (Default: False) - -ms, -multipleIfDirectiveOutputSymbols Generate #if-- files by symbols, split with ','. (Default: null) -``` - -`mpc` targets C# code with `[MessagePackObject]` or `[Union]` annotations. - -```cmd -// Simple Sample: -dotnet mpc -i "..\src\Sandbox.Shared.csproj" -o "MessagePackGenerated.cs" - -// Use force map simulate DynamicContractlessObjectResolver -dotnet mpc -i "..\src\Sandbox.Shared.csproj" -o "MessagePackGenerated.cs" -m -``` +### Customizations -By default, `mpc` generates the resolver as `MessagePack.Resolvers.GeneratedResolver` and formatters as`MessagePack.Formatters.*`. +You can customize the generated source through properties on the `GeneratedMessagePackResolverAttribute`. -Here is the full sample code to register a generated resolver in Unity. - -```csharp -using MessagePack; -using MessagePack.Resolvers; -using UnityEngine; - -public class Startup +```cs +[GeneratedMessagePackResolver] +partial class MyResolver { - static bool serializerRegistered = false; - - [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] - static void Initialize() - { - if (!serializerRegistered) - { - StaticCompositeResolver.Instance.Register( - MessagePack.Resolvers.GeneratedResolver.Instance, - MessagePack.Resolvers.StandardResolver.Instance - ); - - var option = MessagePackSerializerOptions.Standard.WithResolver(StaticCompositeResolver.Instance); - - MessagePackSerializer.DefaultOptions = option; - serializerRegistered = true; - } - } - -#if UNITY_EDITOR - - - [UnityEditor.InitializeOnLoadMethod] - static void EditorInitialize() - { - Initialize(); - } - -#endif } ``` -In Unity, you can use MessagePack CodeGen windows at `Windows -> MessagePack -> CodeGenerator`. +When exposing the generated resolver publicly, consumers outside the library should aggregate the resolver using its `Instance` property, which contains *only* the generated formatters. -![](https://user-images.githubusercontent.com/46207/69414381-f14da400-0d55-11ea-9f8d-9af448d347dc.png) +Two assembly-level attributes exist to help with mixing in your own custom formatters with the automatically generated ones: +- `MessagePackKnownFormatterAttribute` +- `MessagePackAssumedFormattableAttribute` -Install the .NET runtime, install mpc (as a .NET Tool as described above), and execute `dotnet mpc`. Currently this tool is experimental so please tell me your opinion. - -In Xamarin, you can install the [the `MessagePack.MSBuild.Tasks` NuGet package](doc/msbuildtask.md) into your projects to pre-compile fast serialization code and run in environments where JIT compilation is not allowed. - -## RPC - -MessagePack advocated [MessagePack RPC](https://github.com/msgpack-rpc/msgpack-rpc), but work on it has stopped and it is not widely used. +Learn more about using a mix of your own custom formatters and automatically generated ones in [the Analyzer section](#analyzer). ### MagicOnion -I've created a gRPC based MessagePack HTTP/2 RPC streaming framework called [MagicOnion](https://github.com/Cysharp/MagicOnion). gRPC usually communicates with Protocol Buffers using IDL. But MagicOnion uses MessagePack for C# and does not need IDL. When communicating C# to C#, schemaless (or rather C# classes as schema) is better than using IDL. +[MagicOnion](https://github.com/Cysharp/MagicOnion) is a code-first gRPC framework based on grpc-dotnet and MessagePack. gRPC usually communicates with Protocol Buffers using IDL. But MagicOnion uses MessagePack for C# and does not need IDL. When communicating C# to C#, schemaless (or rather C# classes as schema) is better than using IDL. ### StreamJsonRpc The StreamJsonRpc library is based on [JSON-RPC](https://www.jsonrpc.org/) and includes [a pluggable formatter architecture](https://github.com/microsoft/vs-streamjsonrpc/blob/master/doc/extensibility.md#alternative-formatters) and as of v2.3 includes [MessagePack support](https://github.com/microsoft/vs-streamjsonrpc/blob/master/doc/extensibility.md#message-formatterss). ## How to build - See our [contributor's guide](CONTRIBUTING.md). + +## Code of Conduct + +This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community. +For more information see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct). + +## .NET Foundation + +This project is supported by the [.NET Foundation](https://dotnetfoundation.org). diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index 98cea23dd..000000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,38 +0,0 @@ -trigger: - batch: true - branches: - include: - - master - - develop - - 'v?.*' - - 'validate/*' - paths: - exclude: - - doc/ - - '*.md' - - .vscode/ - - .github/ - - azure-pipelines/release.yml - -parameters: -- name: includeMacOS - displayName: Build on macOS - type: boolean - default: false # macOS is often bogged down in Azure Pipelines -- name: RunTests - displayName: Run tests - type: boolean - default: true - -variables: - MSBuildTreatWarningsAsErrors: true - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true - BuildConfiguration: Release - ci_feed: https://pkgs.dev.azure.com/ils0086/MessagePack-CSharp/_packaging/MessagePack-CI/nuget/v3/index.json - NUGET_PACKAGES: $(Agent.TempDirectory)/.nuget/packages/ - -jobs: -- template: azure-pipelines/build.yml - parameters: - includeMacOS: ${{ parameters.includeMacOS }} - RunTests: ${{ parameters.RunTests }} diff --git a/azure-pipelines/Get-ArtifactsStagingDirectory.ps1 b/azure-pipelines/Get-ArtifactsStagingDirectory.ps1 deleted file mode 100644 index 391e5713a..000000000 --- a/azure-pipelines/Get-ArtifactsStagingDirectory.ps1 +++ /dev/null @@ -1,15 +0,0 @@ -Param( - [switch]$CleanIfLocal -) -if ($env:BUILD_ARTIFACTSTAGINGDIRECTORY) { - $ArtifactStagingFolder = $env:BUILD_ARTIFACTSTAGINGDIRECTORY -} elseif ($env:RUNNER_TEMP) { - $ArtifactStagingFolder = "$env:RUNNER_TEMP\_artifacts" -} else { - $ArtifactStagingFolder = [System.IO.Path]::GetFullPath("$PSScriptRoot/../obj/_artifacts") - if ($CleanIfLocal -and (Test-Path $ArtifactStagingFolder)) { - Remove-Item $ArtifactStagingFolder -Recurse -Force - } -} - -$ArtifactStagingFolder diff --git a/azure-pipelines/Get-CodeCovTool.ps1 b/azure-pipelines/Get-CodeCovTool.ps1 deleted file mode 100644 index ca580b4db..000000000 --- a/azure-pipelines/Get-CodeCovTool.ps1 +++ /dev/null @@ -1,86 +0,0 @@ -<# -.SYNOPSIS - Downloads the CodeCov.io uploader tool and returns the path to it. -.PARAMETER AllowSkipVerify - Allows skipping signature verification of the downloaded tool if gpg is not installed. -#> -[CmdletBinding()] -Param( - [switch]$AllowSkipVerify -) - -if ($IsMacOS) { - $codeCovUrl = "https://uploader.codecov.io/latest/macos/codecov" - $toolName = 'codecov' -} -elseif ($IsLinux) { - $codeCovUrl = "https://uploader.codecov.io/latest/linux/codecov" - $toolName = 'codecov' -} -else { - $codeCovUrl = "https://uploader.codecov.io/latest/windows/codecov.exe" - $toolName = 'codecov.exe' -} - -$shaSuffix = ".SHA256SUM" -$sigSuffix = $shaSuffix + ".sig" - -Function Get-FileFromWeb([Uri]$Uri, $OutDir) { - $OutFile = Join-Path $OutDir $Uri.Segments[-1] - if (!(Test-Path $OutFile)) { - Write-Verbose "Downloading $Uri..." - if (!(Test-Path $OutDir)) { New-Item -ItemType Directory -Path $OutDir | Out-Null } - try { - (New-Object System.Net.WebClient).DownloadFile($Uri, $OutFile) - } finally { - # This try/finally causes the script to abort - } - } - - $OutFile -} - -$toolsPath = & "$PSScriptRoot\Get-TempToolsPath.ps1" -$binaryToolsPath = Join-Path $toolsPath codecov -$testingPath = Join-Path $binaryToolsPath unverified -$finalToolPath = Join-Path $binaryToolsPath $toolName - -if (!(Test-Path $finalToolPath)) { - if (Test-Path $testingPath) { - Remove-Item -Recurse -Force $testingPath # ensure we download all matching files - } - $tool = Get-FileFromWeb $codeCovUrl $testingPath - $sha = Get-FileFromWeb "$codeCovUrl$shaSuffix" $testingPath - $sig = Get-FileFromWeb "$codeCovUrl$sigSuffix" $testingPath - $key = Get-FileFromWeb https://keybase.io/codecovsecurity/pgp_keys.asc $testingPath - - if ((Get-Command gpg -ErrorAction SilentlyContinue)) { - Write-Host "Importing codecov key" -ForegroundColor Yellow - gpg --import $key - Write-Host "Verifying signature on codecov hash" -ForegroundColor Yellow - gpg --verify $sig $sha - } else { - if ($AllowSkipVerify) { - Write-Warning "gpg not found. Unable to verify hash signature." - } else { - throw "gpg not found. Unable to verify hash signature. Install gpg or add -AllowSkipVerify to override." - } - } - - Write-Host "Verifying hash on downloaded tool" -ForegroundColor Yellow - $actualHash = (Get-FileHash -Path $tool -Algorithm SHA256).Hash - $expectedHash = (Get-Content $sha).Split()[0] - if ($actualHash -ne $expectedHash) { - # Validation failed. Delete the tool so we can't execute it. - #Remove-Item $codeCovPath - throw "codecov uploader tool failed signature validation." - } - - Copy-Item $tool $finalToolPath - - if ($IsMacOS -or $IsLinux) { - chmod u+x $finalToolPath - } -} - -return $finalToolPath diff --git a/azure-pipelines/Get-NuGetTool.ps1 b/azure-pipelines/Get-NuGetTool.ps1 deleted file mode 100644 index 3097c8736..000000000 --- a/azure-pipelines/Get-NuGetTool.ps1 +++ /dev/null @@ -1,22 +0,0 @@ -<# -.SYNOPSIS - Downloads the NuGet.exe tool and returns the path to it. -.PARAMETER NuGetVersion - The version of the NuGet tool to acquire. -#> -Param( - [Parameter()] - [string]$NuGetVersion='6.4.0' -) - -$toolsPath = & "$PSScriptRoot\Get-TempToolsPath.ps1" -$binaryToolsPath = Join-Path $toolsPath $NuGetVersion -if (!(Test-Path $binaryToolsPath)) { $null = mkdir $binaryToolsPath } -$nugetPath = Join-Path $binaryToolsPath nuget.exe - -if (!(Test-Path $nugetPath)) { - Write-Host "Downloading nuget.exe $NuGetVersion..." -ForegroundColor Yellow - (New-Object System.Net.WebClient).DownloadFile("https://dist.nuget.org/win-x86-commandline/v$NuGetVersion/NuGet.exe", $nugetPath) -} - -return (Resolve-Path $nugetPath).Path diff --git a/azure-pipelines/Get-ProcDump.ps1 b/azure-pipelines/Get-ProcDump.ps1 deleted file mode 100644 index 1493fe4b2..000000000 --- a/azure-pipelines/Get-ProcDump.ps1 +++ /dev/null @@ -1,14 +0,0 @@ -<# -.SYNOPSIS -Downloads 32-bit and 64-bit procdump executables and returns the path to where they were installed. -#> -$version = '0.0.1' -$baseDir = "$PSScriptRoot\..\obj\tools" -$procDumpToolPath = "$baseDir\procdump.$version\bin" -if (-not (Test-Path $procDumpToolPath)) { - if (-not (Test-Path $baseDir)) { New-Item -Type Directory -Path $baseDir | Out-Null } - $baseDir = (Resolve-Path $baseDir).Path # Normalize it - & (& $PSScriptRoot\Get-NuGetTool.ps1) install procdump -version $version -PackageSaveMode nuspec -OutputDirectory $baseDir -Source https://api.nuget.org/v3/index.json | Out-Null -} - -(Resolve-Path $procDumpToolPath).Path diff --git a/azure-pipelines/Get-SymbolFiles.ps1 b/azure-pipelines/Get-SymbolFiles.ps1 deleted file mode 100644 index 0ce229fc2..000000000 --- a/azure-pipelines/Get-SymbolFiles.ps1 +++ /dev/null @@ -1,61 +0,0 @@ -<# -.SYNOPSIS - Collect the list of PDBs built in this repo. -.PARAMETER Path - The directory to recursively search for PDBs. -.PARAMETER Tests - A switch indicating to find PDBs only for test binaries instead of only for shipping shipping binaries. -#> -[CmdletBinding()] -param ( - [parameter(Mandatory=$true)] - [string]$Path, - [switch]$Tests -) - -$ActivityName = "Collecting symbols from $Path" -Write-Progress -Activity $ActivityName -CurrentOperation "Discovery PDB files" -$PDBs = Get-ChildItem -rec "$Path/*.pdb" - -# Filter PDBs to product OR test related. -$testregex = "unittest|tests|\.test\." - -Write-Progress -Activity $ActivityName -CurrentOperation "De-duplicating symbols" -$PDBsByHash = @{} -$i = 0 -$PDBs |% { - Write-Progress -Activity $ActivityName -CurrentOperation "De-duplicating symbols" -PercentComplete (100 * $i / $PDBs.Length) - $hash = Get-FileHash $_ - $i++ - Add-Member -InputObject $_ -MemberType NoteProperty -Name Hash -Value $hash.Hash - Write-Output $_ -} | Sort-Object CreationTime |% { - # De-dupe based on hash. Prefer the first match so we take the first built copy. - if (-not $PDBsByHash.ContainsKey($_.Hash)) { - $PDBsByHash.Add($_.Hash, $_.FullName) - Write-Output $_ - } -} |? { - if ($Tests) { - $_.FullName -match $testregex - } else { - $_.FullName -notmatch $testregex - } -} |% { - # Collect the DLLs/EXEs as well. - $dllPath = "$($_.Directory)/$($_.BaseName).dll" - $exePath = "$($_.Directory)/$($_.BaseName).exe" - if (Test-Path $dllPath) { - $BinaryImagePath = $dllPath - } elseif (Test-Path $exePath) { - $BinaryImagePath = $exePath - } else { - Write-Warning "`"$_`" found with no matching binary file." - $BinaryImagePath = $null - } - - if ($BinaryImagePath) { - Write-Output $BinaryImagePath - Write-Output $_.FullName - } -} diff --git a/azure-pipelines/Get-nbgv.ps1 b/azure-pipelines/Get-nbgv.ps1 deleted file mode 100644 index a5be2cf7c..000000000 --- a/azure-pipelines/Get-nbgv.ps1 +++ /dev/null @@ -1,24 +0,0 @@ -<# -.SYNOPSIS - Gets the path to the nbgv CLI tool, installing it if necessary. -#> -Param( -) - -$existingTool = Get-Command "nbgv" -ErrorAction SilentlyContinue -if ($existingTool) { - return $existingTool.Path -} - -$toolInstallDir = & "$PSScriptRoot/Get-TempToolsPath.ps1" - -$toolPath = "$toolInstallDir/nbgv" -if (!(Test-Path $toolInstallDir)) { New-Item -Path $toolInstallDir -ItemType Directory | Out-Null } - -if (!(Get-Command $toolPath -ErrorAction SilentlyContinue)) { - Write-Host "Installing nbgv to $toolInstallDir" - dotnet tool install --tool-path "$toolInstallDir" nbgv --configfile "$PSScriptRoot/justnugetorg.nuget.config" | Out-Null -} - -# Normalize the path on the way out. -return (Get-Command $toolPath).Path diff --git a/azure-pipelines/Merge-CodeCoverage.ps1 b/azure-pipelines/Merge-CodeCoverage.ps1 deleted file mode 100644 index 02ff12b00..000000000 --- a/azure-pipelines/Merge-CodeCoverage.ps1 +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env pwsh - -<# -.SYNOPSIS - Merges code coverage reports. -.PARAMETER Path - The path(s) to search for Cobertura code coverage reports. -.PARAMETER Format - The format for the merged result. The default is Cobertura -.PARAMETER OutputDir - The directory the merged result will be written to. The default is `coveragereport` in the root of this repo. -#> -[CmdletBinding()] -Param( - [Parameter(Mandatory=$true)] - [string[]]$Path, - [ValidateSet('Badges', 'Clover', 'Cobertura', 'CsvSummary', 'Html', 'Html_Dark', 'Html_Light', 'HtmlChart', 'HtmlInline', 'HtmlInline_AzurePipelines', 'HtmlInline_AzurePipelines_Dark', 'HtmlInline_AzurePipelines_Light', 'HtmlSummary', 'JsonSummary', 'Latex', 'LatexSummary', 'lcov', 'MarkdownSummary', 'MHtml', 'PngChart', 'SonarQube', 'TeamCitySummary', 'TextSummary', 'Xml', 'XmlSummary')] - [string]$Format='Cobertura', - [string]$OutputFile=("$PSScriptRoot/../coveragereport/merged.cobertura.xml") -) - -$RepoRoot = [string](Resolve-Path $PSScriptRoot/..) - -if (!(Test-Path $RepoRoot/obj/dotnet-coverage*)) { - dotnet tool install --tool-path $RepoRoot/obj dotnet-coverage --version 17.4.3 --configfile $PSScriptRoot/justnugetorg.nuget.config -} - -Write-Verbose "Searching $Path for *.cobertura.xml files" -$reports = Get-ChildItem -Recurse $Path -Filter *.cobertura.xml - -if ($reports) { - $reports |% { $_.FullName } |% { - # In addition to replacing {reporoot}, we also normalize on one kind of slash so that the report aggregates data for a file whether data was collected on Windows or not. - $xml = [xml](Get-Content -Path $_) - $xml.coverage.packages.package.classes.class |? { $_.filename} |% { - $_.filename = $_.filename.Replace('{reporoot}', $RepoRoot).Replace([IO.Path]::AltDirectorySeparatorChar, [IO.Path]::DirectorySeparatorChar) - } - - $xml.Save($_) - } - - $Inputs = $reports |% { Resolve-Path -relative $_.FullName } - - if ((Split-Path $OutputFile) -and -not (Test-Path (Split-Path $OutputFile))) { - New-Item -Type Directory -Path (Split-Path $OutputFile) | Out-Null - } - - & "$RepoRoot/obj/dotnet-coverage" merge $Inputs -o $OutputFile -f cobertura -} else { - Write-Error "No reports found to merge." -} diff --git a/azure-pipelines/artifacts/Variables.ps1 b/azure-pipelines/artifacts/Variables.ps1 deleted file mode 100644 index 4bc6d2165..000000000 --- a/azure-pipelines/artifacts/Variables.ps1 +++ /dev/null @@ -1,43 +0,0 @@ -# This artifact captures all variables defined in the ..\variables folder. -# It "snaps" the values of these variables where we can compute them during the build, -# and otherwise captures the scripts to run later during an Azure Pipelines environment release. - -$RepoRoot = [System.IO.Path]::GetFullPath("$PSScriptRoot/../..") -$ArtifactBasePath = "$RepoRoot/obj/_artifacts" -$VariablesArtifactPath = Join-Path $ArtifactBasePath variables -if (-not (Test-Path $VariablesArtifactPath)) { New-Item -ItemType Directory -Path $VariablesArtifactPath | Out-Null } - -# Copy variables, either by value if the value is calculable now, or by script -Get-ChildItem "$PSScriptRoot/../variables" |% { - $value = $null - if (-not $_.BaseName.StartsWith('_')) { # Skip trying to interpret special scripts - # First check the environment variables in case the variable was set in a queued build - # Always use all caps for env var access because Azure Pipelines converts variables to upper-case for env vars, - # and on non-Windows env vars are case sensitive. - $envVarName = $_.BaseName.ToUpper() - if (Test-Path env:$envVarName) { - $value = Get-Content "env:$envVarName" - } - - # If that didn't give us anything, try executing the script right now from its original position - if (-not $value) { - $value = & $_.FullName - } - - if ($value) { - # We got something, so wrap it with quotes so it's treated like a literal value. - $value = "'$value'" - } - } - - # If that didn't get us anything, just copy the script itself - if (-not $value) { - $value = Get-Content -Path $_.FullName - } - - Set-Content -Path "$VariablesArtifactPath/$($_.Name)" -Value $value -} - -@{ - "$VariablesArtifactPath" = (Get-ChildItem $VariablesArtifactPath -Recurse); -} diff --git a/azure-pipelines/artifacts/_all.ps1 b/azure-pipelines/artifacts/_all.ps1 deleted file mode 100755 index 9a22a1d08..000000000 --- a/azure-pipelines/artifacts/_all.ps1 +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env pwsh - -<# -.SYNOPSIS - This script returns all the artifacts that should be collected after a build. - Each powershell artifact is expressed as an object with these properties: - Source - the full path to the source file - ArtifactName - the name of the artifact to upload to - ContainerFolder - the relative path within the artifact in which the file should appear - Each artifact aggregating .ps1 script should return a hashtable: - Key = path to the directory from which relative paths within the artifact should be calculated - Value = an array of paths (absolute or relative to the BaseDirectory) to files to include in the artifact. - FileInfo objects are also allowed. -.PARAMETER Force - Executes artifact scripts even if they have already been staged. -#> - -[CmdletBinding(SupportsShouldProcess = $true)] -param ( - [string]$ArtifactNameSuffix, - [switch]$Force -) - -Function EnsureTrailingSlash($path) { - if ($path.length -gt 0 -and !$path.EndsWith('\') -and !$path.EndsWith('/')) { - $path = $path + [IO.Path]::DirectorySeparatorChar - } - - $path.Replace('\', [IO.Path]::DirectorySeparatorChar) -} - -Function Test-ArtifactStaged($artifactName) { - $varName = "ARTIFACTSTAGED_$($artifactName.ToUpper())" - Test-Path "env:$varName" -} - -Get-ChildItem "$PSScriptRoot\*.ps1" -Exclude "_*" -Recurse | % { - $ArtifactName = $_.BaseName - if ($Force -or !(Test-ArtifactStaged($ArtifactName + $ArtifactNameSuffix))) { - $totalFileCount = 0 - Write-Verbose "Collecting file list for artifact $($_.BaseName)" - $fileGroups = & $_ - if ($fileGroups) { - $fileGroups.GetEnumerator() | % { - $BaseDirectory = New-Object Uri ((EnsureTrailingSlash $_.Key.ToString()), [UriKind]::Absolute) - $_.Value | ? { $_ } | % { - if ($_.GetType() -eq [IO.FileInfo] -or $_.GetType() -eq [IO.DirectoryInfo]) { - $_ = $_.FullName - } - - $artifact = New-Object -TypeName PSObject - Add-Member -InputObject $artifact -MemberType NoteProperty -Name ArtifactName -Value $ArtifactName - - $SourceFullPath = New-Object Uri ($BaseDirectory, $_) - Add-Member -InputObject $artifact -MemberType NoteProperty -Name Source -Value $SourceFullPath.LocalPath - - $RelativePath = [Uri]::UnescapeDataString($BaseDirectory.MakeRelative($SourceFullPath)) - Add-Member -InputObject $artifact -MemberType NoteProperty -Name ContainerFolder -Value (Split-Path $RelativePath) - - Write-Output $artifact - $totalFileCount += 1 - } - } - } - - if ($totalFileCount -eq 0) { - Write-Warning "No files found for the `"$ArtifactName`" artifact." - } - } else { - Write-Host "Skipping $ArtifactName because it has already been staged." -ForegroundColor DarkGray - } -} diff --git a/azure-pipelines/artifacts/_pipelines.ps1 b/azure-pipelines/artifacts/_pipelines.ps1 deleted file mode 100644 index 2d3338b24..000000000 --- a/azure-pipelines/artifacts/_pipelines.ps1 +++ /dev/null @@ -1,44 +0,0 @@ -<# -.SYNOPSIS - This script translates all the artifacts described by _all.ps1 - into commands that instruct Azure Pipelines to actually collect those artifacts. -#> - -[CmdletBinding()] -param ( - [string]$ArtifactNameSuffix, - [switch]$StageOnly -) - -Function Set-PipelineVariable($name, $value) { - if ((Test-Path "Env:\$name") -and (Get-Item "Env:\$name").Value -eq $value) { - return # already set - } - - #New-Item -Path "Env:\$name".ToUpper() -Value $value -Force | Out-Null - Write-Host "##vso[task.setvariable variable=$name]$value" -} - -Function Test-ArtifactUploaded($artifactName) { - $varName = "ARTIFACTUPLOADED_$($artifactName.ToUpper())" - Test-Path "env:$varName" -} - -& "$PSScriptRoot/_stage_all.ps1" -ArtifactNameSuffix $ArtifactNameSuffix |% { - # Set a variable which will out-live this script so that a subsequent attempt to collect and upload artifacts - # will skip this one from a check in the _all.ps1 script. - Set-PipelineVariable "ARTIFACTSTAGED_$($_.Name.ToUpper())" 'true' - Write-Host "Staged artifact $($_.Name) to $($_.Path)" - - if (!$StageOnly) { - if (Test-ArtifactUploaded $_.Name) { - Write-Host "Skipping $($_.Name) because it has already been uploaded." -ForegroundColor DarkGray - } else { - Write-Host "##vso[artifact.upload containerfolder=$($_.Name);artifactname=$($_.Name);]$($_.Path)" - - # Set a variable which will out-live this script so that a subsequent attempt to collect and upload artifacts - # will skip this one from a check in the _all.ps1 script. - Set-PipelineVariable "ARTIFACTUPLOADED_$($_.Name.ToUpper())" 'true' - } - } -} diff --git a/azure-pipelines/artifacts/_stage_all.ps1 b/azure-pipelines/artifacts/_stage_all.ps1 deleted file mode 100644 index d81d16d46..000000000 --- a/azure-pipelines/artifacts/_stage_all.ps1 +++ /dev/null @@ -1,60 +0,0 @@ -<# -.SYNOPSIS - This script links all the artifacts described by _all.ps1 - into a staging directory, reading for uploading to a cloud build artifact store. - It returns a sequence of objects with Name and Path properties. -#> - -[CmdletBinding()] -param ( - [string]$ArtifactNameSuffix -) - -$ArtifactStagingFolder = & "$PSScriptRoot/../Get-ArtifactsStagingDirectory.ps1" -CleanIfLocal - -function Create-SymbolicLink { - param ( - $Link, - $Target - ) - - if ($Link -eq $Target) { - return - } - - if (Test-Path $Link) { Remove-Item $Link } - $LinkContainer = Split-Path $Link -Parent - if (!(Test-Path $LinkContainer)) { mkdir $LinkContainer } - if ($IsMacOS -or $IsLinux) { - ln $Target $Link | Out-Null - } else { - cmd /c "mklink `"$Link`" `"$Target`"" | Out-Null - } -} - -# Stage all artifacts -$Artifacts = & "$PSScriptRoot\_all.ps1" -ArtifactNameSuffix $ArtifactNameSuffix -$Artifacts |% { - $DestinationFolder = [System.IO.Path]::GetFullPath("$ArtifactStagingFolder/$($_.ArtifactName)$ArtifactNameSuffix/$($_.ContainerFolder)").TrimEnd('\') - $Name = "$(Split-Path $_.Source -Leaf)" - - #Write-Host "$($_.Source) -> $($_.ArtifactName)\$($_.ContainerFolder)" -ForegroundColor Yellow - - if (-not (Test-Path $DestinationFolder)) { New-Item -ItemType Directory -Path $DestinationFolder | Out-Null } - if (Test-Path -PathType Leaf $_.Source) { # skip folders - Create-SymbolicLink -Link (Join-Path $DestinationFolder $Name) -Target $_.Source - } -} - -$ArtifactNames = $Artifacts |% { "$($_.ArtifactName)$ArtifactNameSuffix" } -$ArtifactNames += Get-ChildItem env:ARTIFACTSTAGED_* |% { - # Return from ALLCAPS to the actual capitalization used for the artifact. - $artifactNameAllCaps = "$($_.Name.Substring('ARTIFACTSTAGED_'.Length))" - (Get-ChildItem $ArtifactStagingFolder\$artifactNameAllCaps* -Filter $artifactNameAllCaps).Name -} -$ArtifactNames | Get-Unique |% { - $artifact = New-Object -TypeName PSObject - Add-Member -InputObject $artifact -MemberType NoteProperty -Name Name -Value $_ - Add-Member -InputObject $artifact -MemberType NoteProperty -Name Path -Value (Join-Path $ArtifactStagingFolder $_) - Write-Output $artifact -} diff --git a/azure-pipelines/artifacts/build_logs.ps1 b/azure-pipelines/artifacts/build_logs.ps1 deleted file mode 100644 index f05358e03..000000000 --- a/azure-pipelines/artifacts/build_logs.ps1 +++ /dev/null @@ -1,7 +0,0 @@ -$ArtifactStagingFolder = & "$PSScriptRoot/../Get-ArtifactsStagingDirectory.ps1" - -if (!(Test-Path $ArtifactStagingFolder/build_logs)) { return } - -@{ - "$ArtifactStagingFolder/build_logs" = (Get-ChildItem -Recurse "$ArtifactStagingFolder/build_logs") -} diff --git a/azure-pipelines/artifacts/coverageResults.ps1 b/azure-pipelines/artifacts/coverageResults.ps1 deleted file mode 100644 index d2fee5016..000000000 --- a/azure-pipelines/artifacts/coverageResults.ps1 +++ /dev/null @@ -1,23 +0,0 @@ -$RepoRoot = [System.IO.Path]::GetFullPath("$PSScriptRoot\..\..") - -$coverageFiles = @(Get-ChildItem "$RepoRoot/tests/*.cobertura.xml" -Recurse | Where {$_.FullName -notlike "*/In/*" -and $_.FullName -notlike "*\In\*" }) - -# Prepare code coverage reports for merging on another machine -if ($env:SYSTEM_DEFAULTWORKINGDIRECTORY) { - Write-Host "Substituting $env:SYSTEM_DEFAULTWORKINGDIRECTORY with `"{reporoot}`"" - $coverageFiles |% { - $content = Get-Content -Path $_ |% { $_ -Replace [regex]::Escape($env:SYSTEM_DEFAULTWORKINGDIRECTORY), "{reporoot}" } - Set-Content -Path $_ -Value $content -Encoding UTF8 - } -} else { - Write-Warning "coverageResults: Azure Pipelines not detected. Machine-neutral token replacement skipped." -} - -if (!((Test-Path $RepoRoot\bin) -and (Test-Path $RepoRoot\obj))) { return } - -@{ - $RepoRoot = ( - $coverageFiles + - (Get-ChildItem "$RepoRoot\obj\*.cs" -Recurse) - ); -} diff --git a/azure-pipelines/artifacts/deployables.ps1 b/azure-pipelines/artifacts/deployables.ps1 deleted file mode 100644 index 94c48cdd9..000000000 --- a/azure-pipelines/artifacts/deployables.ps1 +++ /dev/null @@ -1,13 +0,0 @@ -$RepoRoot = [System.IO.Path]::GetFullPath("$PSScriptRoot\..\..") -$BuildConfiguration = $env:BUILDCONFIGURATION -if (!$BuildConfiguration) { - $BuildConfiguration = 'Debug' -} - -$PackagesRoot = "$RepoRoot/bin/Packages/$BuildConfiguration" - -if (!(Test-Path $PackagesRoot)) { return } - -@{ - "$PackagesRoot" = (Get-ChildItem $PackagesRoot -Recurse) -} diff --git a/azure-pipelines/artifacts/projectAssetsJson.ps1 b/azure-pipelines/artifacts/projectAssetsJson.ps1 deleted file mode 100644 index d2e85ffbe..000000000 --- a/azure-pipelines/artifacts/projectAssetsJson.ps1 +++ /dev/null @@ -1,9 +0,0 @@ -$ObjRoot = [System.IO.Path]::GetFullPath("$PSScriptRoot\..\..\obj") - -if (!(Test-Path $ObjRoot)) { return } - -@{ - "$ObjRoot" = ( - (Get-ChildItem "$ObjRoot\project.assets.json" -Recurse) - ); -} diff --git a/azure-pipelines/artifacts/symbols.ps1 b/azure-pipelines/artifacts/symbols.ps1 deleted file mode 100644 index 9e2c7bd5b..000000000 --- a/azure-pipelines/artifacts/symbols.ps1 +++ /dev/null @@ -1,7 +0,0 @@ -$BinPath = [System.IO.Path]::GetFullPath("$PSScriptRoot/../../bin") -if (!(Test-Path $BinPath)) { return } -$symbolfiles = & "$PSScriptRoot/../Get-SymbolFiles.ps1" -Path $BinPath | Get-Unique - -@{ - "$BinPath" = $SymbolFiles; -} diff --git a/azure-pipelines/artifacts/testResults.ps1 b/azure-pipelines/artifacts/testResults.ps1 deleted file mode 100644 index 6c042043a..000000000 --- a/azure-pipelines/artifacts/testResults.ps1 +++ /dev/null @@ -1,15 +0,0 @@ -[CmdletBinding()] -Param( -) - -$result = @{} - -$testRoot = Resolve-Path "$PSScriptRoot\..\..\tests" -$result[$testRoot] = (Get-ChildItem "$testRoot\TestResults" -Recurse -Directory | Get-ChildItem -Recurse -File) - -$testlogsPath = "$env:BUILD_ARTIFACTSTAGINGDIRECTORY\test_logs" -if (Test-Path $testlogsPath) { - $result[$testlogsPath] = Get-ChildItem "$testlogsPath\*"; -} - -$result diff --git a/azure-pipelines/artifacts/test_symbols.ps1 b/azure-pipelines/artifacts/test_symbols.ps1 deleted file mode 100644 index ce2b6481c..000000000 --- a/azure-pipelines/artifacts/test_symbols.ps1 +++ /dev/null @@ -1,7 +0,0 @@ -$BinPath = [System.IO.Path]::GetFullPath("$PSScriptRoot/../../bin") -if (!(Test-Path $BinPath)) { return } -$symbolfiles = & "$PSScriptRoot/../Get-SymbolFiles.ps1" -Path $BinPath -Tests | Get-Unique - -@{ - "$BinPath" = $SymbolFiles; -} diff --git a/azure-pipelines/build.yml b/azure-pipelines/build.yml deleted file mode 100644 index 4021879d8..000000000 --- a/azure-pipelines/build.yml +++ /dev/null @@ -1,93 +0,0 @@ -parameters: -- name: windowsPool - type: object - default: - vmImage: windows-2022 -- name: includeMacOS -- name: RunTests - type: boolean - default: true - -jobs: -- job: Windows - pool: ${{ parameters.windowsPool }} - steps: - - checkout: self - fetchDepth: 0 # avoid shallow clone so nbgv can do its work. - clean: true - - template: install-dependencies.yml - - - powershell: '& (./azure-pipelines/Get-nbgv.ps1) cloud -c' - displayName: ⚙ Set build number - - - template: dotnet.yml - parameters: - RunTests: ${{ parameters.RunTests }} - -- job: Linux - pool: - vmImage: Ubuntu 20.04 - steps: - - checkout: self - fetchDepth: 0 # avoid shallow clone so nbgv can do its work. - clean: true - - template: install-dependencies.yml - - template: dotnet.yml - parameters: - RunTests: ${{ parameters.RunTests }} - -- job: macOS - condition: ${{ parameters.includeMacOS }} - pool: - vmImage: macOS-12 - steps: - - checkout: self - fetchDepth: 0 # avoid shallow clone so nbgv can do its work. - clean: true - - template: install-dependencies.yml - - template: dotnet.yml - parameters: - RunTests: ${{ parameters.RunTests }} - -# This job ensures that we're running mpc regularly on the generated code that we check in. -# It also helps exercise mpc so bugs don't go unnoticed. -- job: codegen_diff - pool: - vmImage: ubuntu-22.04 - steps: - - checkout: self - clean: true - - template: install-dependencies.yml - - pwsh: sandbox/codegen.ps1 - displayName: 🏭 Regenerate checked-in code - - bash: | - git add -u . # compare after applying git EOL normalization - git diff --cached --exit-code --stat \ - || (echo "##[error] found changed files after build. Please run 'sandbox/codegen.ps1'" \ - "and check in all changes" \ - && git diff --cached \ - && exit 1) - displayName: 🔍 Check for uncommitted changes - -- job: WrapUp - dependsOn: - - Windows - - Linux - - macOS - pool: ${{ parameters.windowsPool }} # Use Windows agent because PublishSymbols task requires it (https://github.com/microsoft/azure-pipelines-tasks/issues/13821). - condition: succeededOrFailed() - steps: - - checkout: self - fetchDepth: 0 # avoid shallow clone so nbgv can do its work. - clean: true - - template: install-dependencies.yml - parameters: - initArgs: -NoRestore - - template: publish-symbols.yml - parameters: - includeMacOS: ${{ parameters.includeMacOS }} - - ${{ if parameters.RunTests }}: - - template: publish-codecoverage.yml - parameters: - includeMacOS: ${{ parameters.includeMacOS }} - - template: publish-deployables.yml diff --git a/azure-pipelines/dotnet-test-cloud.ps1 b/azure-pipelines/dotnet-test-cloud.ps1 deleted file mode 100644 index 13f973cd4..000000000 --- a/azure-pipelines/dotnet-test-cloud.ps1 +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env pwsh - -<# -.SYNOPSIS - Runs tests as they are run in cloud test runs. -.PARAMETER Configuration - The configuration within which to run tests -.PARAMETER Agent - The name of the agent. This is used in preparing test run titles. -.PARAMETER PublishResults - A switch to publish results to Azure Pipelines. -.PARAMETER x86 - A switch to run the tests in an x86 process. -.PARAMETER dotnet32 - The path to a 32-bit dotnet executable to use. -#> -[CmdletBinding()] -Param( - [string]$Configuration='Debug', - [string]$Agent='Local', - [switch]$PublishResults, - [switch]$x86, - [string]$dotnet32 -) - -$RepoRoot = (Resolve-Path "$PSScriptRoot/..").Path -$ArtifactStagingFolder = & "$PSScriptRoot/Get-ArtifactsStagingDirectory.ps1" - -$dotnet = 'dotnet' -if ($x86) { - $x86RunTitleSuffix = ", x86" - if ($dotnet32) { - $dotnet = $dotnet32 - } else { - $dotnet32Possibilities = "$PSScriptRoot\../obj/tools/x86/.dotnet/dotnet.exe", "$env:AGENT_TOOLSDIRECTORY/x86/dotnet/dotnet.exe", "${env:ProgramFiles(x86)}\dotnet\dotnet.exe" - $dotnet32Matches = $dotnet32Possibilities |? { Test-Path $_ } - if ($dotnet32Matches) { - $dotnet = Resolve-Path @($dotnet32Matches)[0] - Write-Host "Running tests using `"$dotnet`"" -ForegroundColor DarkGray - } else { - Write-Error "Unable to find 32-bit dotnet.exe" - return 1 - } - } -} - -& $dotnet test $RepoRoot ` - --no-build ` - -c $Configuration ` - --filter "TestCategory!=FailsInCloudTest" ` - --collect "Code Coverage;Format=cobertura" ` - --settings "$PSScriptRoot/test.runsettings" ` - --blame-hang-timeout 60s ` - --blame-crash ` - -bl:"$ArtifactStagingFolder/build_logs/test.binlog" ` - --diag "$ArtifactStagingFolder/test_logs/diag.log;TraceLevel=info" ` - --logger trx ` - -$unknownCounter = 0 -Get-ChildItem -Recurse -Path $RepoRoot\tests\*.trx |% { - Copy-Item $_ -Destination $ArtifactStagingFolder/test_logs/ - - if ($PublishResults) { - $x = [xml](Get-Content -Path $_) - $runTitle = $null - if ($x.TestRun.TestDefinitions -and $x.TestRun.TestDefinitions.GetElementsByTagName('UnitTest')) { - $storage = $x.TestRun.TestDefinitions.GetElementsByTagName('UnitTest')[0].storage -replace '\\','/' - if ($storage -match '/(?net[^/]+)/(?:(?[^/]+)/)?(?[^/]+)\.dll$') { - if ($matches.rid) { - $runTitle = "$($matches.lib) ($($matches.tfm), $($matches.rid), $Agent)" - } else { - $runTitle = "$($matches.lib) ($($matches.tfm)$x86RunTitleSuffix, $Agent)" - } - } - } - if (!$runTitle) { - $unknownCounter += 1; - $runTitle = "unknown$unknownCounter ($Agent$x86RunTitleSuffix)"; - } - - Write-Host "##vso[results.publish type=VSTest;runTitle=$runTitle;publishRunAttachments=true;resultFiles=$_;failTaskOnFailedTests=true;testRunSystem=VSTS - PTR;]" - } -} diff --git a/azure-pipelines/dotnet.yml b/azure-pipelines/dotnet.yml deleted file mode 100644 index 79babd4d4..000000000 --- a/azure-pipelines/dotnet.yml +++ /dev/null @@ -1,30 +0,0 @@ -parameters: - RunTests: - -steps: - -- script: dotnet build -t:build,pack --no-restore -c $(BuildConfiguration) /bl:"$(Build.ArtifactStagingDirectory)/build_logs/build.binlog" - displayName: 🛠 dotnet build - -- powershell: azure-pipelines/dotnet-test-cloud.ps1 -Configuration $(BuildConfiguration) -Agent $(Agent.JobName) -PublishResults - displayName: 🧪 dotnet test - condition: and(succeeded(), ${{ parameters.RunTests }}) - -- powershell: azure-pipelines/variables/_pipelines.ps1 - failOnStderr: true - displayName: ⚙ Update pipeline variables based on build outputs - condition: succeededOrFailed() - -- powershell: azure-pipelines/artifacts/_pipelines.ps1 -ArtifactNameSuffix "-$(Agent.JobName)" -Verbose - failOnStderr: true - displayName: 📢 Publish artifacts - condition: succeededOrFailed() - -- ${{ if and(ne(variables['codecov_token'], ''), parameters.RunTests) }}: - - powershell: | - $ArtifactStagingFolder = & "azure-pipelines/Get-ArtifactsStagingDirectory.ps1" - $CoverageResultsFolder = Join-Path $ArtifactStagingFolder "coverageResults-$(Agent.JobName)" - azure-pipelines/publish-CodeCov.ps1 -CodeCovToken "$(codecov_token)" -PathToCodeCoverage "$CoverageResultsFolder" -Name "$(Agent.JobName) Coverage Results" -Flags "$(Agent.JobName)Host,$(BuildConfiguration)" - displayName: 📢 Publish code coverage results to codecov.io - timeoutInMinutes: 3 - continueOnError: true diff --git a/azure-pipelines/install-dependencies.yml b/azure-pipelines/install-dependencies.yml deleted file mode 100644 index 817826689..000000000 --- a/azure-pipelines/install-dependencies.yml +++ /dev/null @@ -1,25 +0,0 @@ -parameters: - initArgs: - -steps: - -- task: NuGetAuthenticate@1 - displayName: 🔏 Authenticate NuGet feeds - inputs: - forceReinstallCredentialProvider: true - -- powershell: | - $AccessToken = '$(System.AccessToken)' # Avoid specifying the access token directly on the init.ps1 command line to avoid it showing up in errors - .\init.ps1 -AccessToken $AccessToken ${{ parameters['initArgs'] }} -UpgradePrerequisites -NoNuGetCredProvider - dotnet --info - - # Print mono version if it is present. - if (Get-Command mono -ErrorAction SilentlyContinue) { - mono --version - } - displayName: ⚙ Install prerequisites - -- powershell: azure-pipelines/variables/_pipelines.ps1 - failOnStderr: true - displayName: ⚙ Set pipeline variables based on source - name: SetPipelineVariables diff --git a/azure-pipelines/justnugetorg.nuget.config b/azure-pipelines/justnugetorg.nuget.config deleted file mode 100644 index 765346e53..000000000 --- a/azure-pipelines/justnugetorg.nuget.config +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/azure-pipelines/publish-CodeCov.ps1 b/azure-pipelines/publish-CodeCov.ps1 deleted file mode 100644 index 9926f0188..000000000 --- a/azure-pipelines/publish-CodeCov.ps1 +++ /dev/null @@ -1,30 +0,0 @@ -<# -.SYNOPSIS - Uploads code coverage to codecov.io -.PARAMETER CodeCovToken - Code coverage token to use -.PARAMETER PathToCodeCoverage - Path to root of code coverage files -.PARAMETER Name - Name to upload with codecoverge -.PARAMETER Flags - Flags to upload with codecoverge -#> -[CmdletBinding()] -Param ( - [Parameter(Mandatory=$true)] - [string]$CodeCovToken, - [Parameter(Mandatory=$true)] - [string]$PathToCodeCoverage, - [string]$Name, - [string]$Flags -) - -$RepoRoot = (Resolve-Path "$PSScriptRoot/..").Path - -Get-ChildItem -Recurse -Path $PathToCodeCoverage -Filter "*.cobertura.xml" | % { - $relativeFilePath = Resolve-Path -relative $_.FullName - - Write-Host "Uploading: $relativeFilePath" -ForegroundColor Yellow - & (& "$PSScriptRoot/Get-CodeCovTool.ps1") -t $CodeCovToken -f $relativeFilePath -R $RepoRoot -F $Flags -n $Name -} diff --git a/azure-pipelines/publish-codecoverage.yml b/azure-pipelines/publish-codecoverage.yml deleted file mode 100644 index fbb6a39a7..000000000 --- a/azure-pipelines/publish-codecoverage.yml +++ /dev/null @@ -1,25 +0,0 @@ -parameters: - includeMacOS: - -steps: -- download: current - artifact: coverageResults-Windows - displayName: 🔻 Download Windows code coverage results - continueOnError: true -- download: current - artifact: coverageResults-Linux - displayName: 🔻 Download Linux code coverage results - continueOnError: true -- download: current - artifact: coverageResults-macOS - displayName: 🔻 Download macOS code coverage results - continueOnError: true - condition: and(succeeded(), ${{ parameters.includeMacOS }}) -- powershell: azure-pipelines/Merge-CodeCoverage.ps1 -Path '$(Pipeline.Workspace)' -OutputFile coveragereport/merged.cobertura.xml -Format Cobertura -Verbose - displayName: ⚙ Merge coverage -- task: PublishCodeCoverageResults@1 - displayName: 📢 Publish code coverage results to Azure DevOps - inputs: - codeCoverageTool: cobertura - summaryFileLocation: coveragereport/merged.cobertura.xml - failIfCoverageEmpty: true diff --git a/azure-pipelines/publish-deployables.yml b/azure-pipelines/publish-deployables.yml deleted file mode 100644 index 31e80a437..000000000 --- a/azure-pipelines/publish-deployables.yml +++ /dev/null @@ -1,8 +0,0 @@ -steps: -- download: current - displayName: 🔻 Download deployables - artifact: deployables-Windows - -- powershell: dotnet nuget push "$(Resolve-Path '$(Pipeline.Workspace)\deployables-Windows\')*.nupkg" -s $(ci_feed) -k azdo --skip-duplicate - displayName: 📦 Push packages to CI feed - condition: and(succeeded(), ne(variables['ci_feed'], ''), ne(variables['Build.Reason'], 'PullRequest')) diff --git a/azure-pipelines/publish-symbols.yml b/azure-pipelines/publish-symbols.yml deleted file mode 100644 index 00c188fc3..000000000 --- a/azure-pipelines/publish-symbols.yml +++ /dev/null @@ -1,59 +0,0 @@ -parameters: - includeMacOS: - -steps: -- task: DownloadPipelineArtifact@2 - inputs: - artifact: symbols-Windows - path: $(Pipeline.Workspace)/symbols/Windows - displayName: 🔻 Download Windows symbols - continueOnError: true -- task: DownloadPipelineArtifact@2 - inputs: - artifact: symbols-Linux - path: $(Pipeline.Workspace)/symbols/Linux - displayName: 🔻 Download Linux symbols - continueOnError: true -- task: DownloadPipelineArtifact@2 - inputs: - artifact: symbols-macOS - path: $(Pipeline.Workspace)/symbols/macOS - displayName: 🔻 Download macOS symbols - continueOnError: true - condition: ${{ parameters.includeMacOS }} - -- task: DownloadPipelineArtifact@2 - inputs: - artifact: test_symbols-Windows - path: $(Pipeline.Workspace)/test_symbols/Windows - displayName: 🔻 Download Windows test symbols - continueOnError: true -- task: DownloadPipelineArtifact@2 - inputs: - artifact: test_symbols-Linux - path: $(Pipeline.Workspace)/test_symbols/Linux - displayName: 🔻 Download Linux test symbols - continueOnError: true -- task: DownloadPipelineArtifact@2 - inputs: - artifact: test_symbols-macOS - path: $(Pipeline.Workspace)/test_symbols/macOS - displayName: 🔻 Download macOS test symbols - continueOnError: true - condition: ${{ parameters.includeMacOS }} - -- task: PublishSymbols@2 - inputs: - SymbolsFolder: $(Pipeline.Workspace)/symbols - SearchPattern: '**/*.pdb' - IndexSources: false - SymbolServerType: TeamServices - displayName: 📢 Publish symbols - -- task: PublishSymbols@2 - inputs: - SymbolsFolder: $(Pipeline.Workspace)/test_symbols - SearchPattern: '**/*.pdb' - IndexSources: false - SymbolServerType: TeamServices - displayName: 📢 Publish test symbols diff --git a/azure-pipelines/release.yml b/azure-pipelines/release.yml deleted file mode 100644 index be5c4f614..000000000 --- a/azure-pipelines/release.yml +++ /dev/null @@ -1,62 +0,0 @@ -trigger: none # We only want to trigger manually or based on resources -pr: none - -resources: - pipelines: - - pipeline: CI - source: MessagePack-CSharp-CI - trigger: - tags: - - auto-release - -variables: -- group: Publishing secrets - -jobs: -- job: release - pool: - vmImage: ubuntu-latest - steps: - - checkout: none - - powershell: | - Write-Host "##vso[build.updatebuildnumber]$(resources.pipeline.CI.runName)" - if ('$(resources.pipeline.CI.runName)'.Contains('-')) { - Write-Host "##vso[task.setvariable variable=IsPrerelease]true" - } else { - Write-Host "##vso[task.setvariable variable=IsPrerelease]false" - } - displayName: ⚙ Set up pipeline - - task: UseDotNet@2 - displayName: ⚙ Install .NET SDK - inputs: - packageType: sdk - version: 6.x - - download: CI - artifact: deployables-Windows - displayName: 🔻 Download deployables-Windows artifact - patterns: 'deployables-Windows/*' - - task: GitHubRelease@1 - displayName: 📢 GitHub release (create) - inputs: - gitHubConnection: AArnott github - repositoryName: $(Build.Repository.Name) - target: $(resources.pipeline.CI.sourceCommit) - tagSource: userSpecifiedTag - tag: v$(resources.pipeline.CI.runName) - title: v$(resources.pipeline.CI.runName) - isDraft: true # After running this step, visit the new draft release, edit, and publish. - isPreRelease: $(IsPrerelease) - assets: | - $(Pipeline.Workspace)/CI/deployables-Windows/*.nupkg - $(Pipeline.Workspace)/CI/unity/*.unitypackage - changeLogCompareToRelease: lastNonDraftRelease - changeLogType: issueBased - changeLogLabels: | - [ - { "label" : "breaking change", "displayName" : "Breaking changes", "state" : "closed" }, - { "label" : "bug", "displayName" : "Fixes", "state" : "closed" }, - { "label" : "enhancement", "displayName": "Enhancements", "state" : "closed" } - ] - - script: dotnet nuget push $(Pipeline.Workspace)/CI/deployables-Windows/*.nupkg -s https://api.nuget.org/v3/index.json --api-key $(NuGetOrgApiKey) --skip-duplicate - displayName: 📦 Push packages to nuget.org - condition: and(succeeded(), ne(variables['NuGetOrgApiKey'], '')) diff --git a/azure-pipelines/test.runsettings b/azure-pipelines/test.runsettings deleted file mode 100644 index 4e24a0a65..000000000 --- a/azure-pipelines/test.runsettings +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - \.dll$ - \.exe$ - - - xunit\..* - - - - - ^System\.Diagnostics\.DebuggerHiddenAttribute$ - ^System\.Diagnostics\.DebuggerNonUserCodeAttribute$ - ^System\.CodeDom\.Compiler\.GeneratedCodeAttribute$ - ^System\.Diagnostics\.CodeAnalysis\.ExcludeFromCodeCoverageAttribute$ - - - - - True - - True - - True - - False - - False - - False - - True - - - - - - diff --git a/azure-pipelines/variables/DotNetSdkVersion.ps1 b/azure-pipelines/variables/DotNetSdkVersion.ps1 deleted file mode 100644 index b213fbc27..000000000 --- a/azure-pipelines/variables/DotNetSdkVersion.ps1 +++ /dev/null @@ -1,2 +0,0 @@ -$globalJson = Get-Content -Path "$PSScriptRoot\..\..\global.json" | ConvertFrom-Json -$globalJson.sdk.version diff --git a/azure-pipelines/variables/_all.ps1 b/azure-pipelines/variables/_all.ps1 deleted file mode 100755 index cc6e88105..000000000 --- a/azure-pipelines/variables/_all.ps1 +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env pwsh - -<# -.SYNOPSIS - This script returns a hashtable of build variables that should be set - at the start of a build or release definition's execution. -#> - -[CmdletBinding(SupportsShouldProcess = $true)] -param ( -) - -$vars = @{} - -Get-ChildItem "$PSScriptRoot\*.ps1" -Exclude "_*" |% { - Write-Host "Computing $($_.BaseName) variable" - $vars[$_.BaseName] = & $_ -} - -$vars diff --git a/azure-pipelines/variables/_pipelines.ps1 b/azure-pipelines/variables/_pipelines.ps1 deleted file mode 100644 index 11748b81b..000000000 --- a/azure-pipelines/variables/_pipelines.ps1 +++ /dev/null @@ -1,31 +0,0 @@ -<# -.SYNOPSIS - This script translates the variables returned by the _all.ps1 script - into commands that instruct Azure Pipelines to actually set those variables for other pipeline tasks to consume. - - The build or release definition may have set these variables to override - what the build would do. So only set them if they have not already been set. -#> - -[CmdletBinding()] -param ( -) - -(& "$PSScriptRoot\_all.ps1").GetEnumerator() |% { - # Always use ALL CAPS for env var names since Azure Pipelines converts variable names to all caps and on non-Windows OS, env vars are case sensitive. - $keyCaps = $_.Key.ToUpper() - if ((Test-Path "env:$keyCaps") -and (Get-Content "env:$keyCaps")) { - Write-Host "Skipping setting $keyCaps because variable is already set to '$(Get-Content env:$keyCaps)'." -ForegroundColor Cyan - } else { - Write-Host "$keyCaps=$($_.Value)" -ForegroundColor Yellow - if ($env:TF_BUILD) { - # Create two variables: the first that can be used by its simple name and accessible only within this job. - Write-Host "##vso[task.setvariable variable=$keyCaps]$($_.Value)" - # and the second that works across jobs and stages but must be fully qualified when referenced. - Write-Host "##vso[task.setvariable variable=$keyCaps;isOutput=true]$($_.Value)" - } elseif ($env:GITHUB_ACTIONS) { - Add-Content -Path $env:GITHUB_ENV -Value "$keyCaps=$($_.Value)" - } - Set-Item -Path "env:$keyCaps" -Value $_.Value - } -} diff --git a/benchmark/CollectionsMarshalBenchmark/CollectionsMarshalBenchmark.csproj b/benchmark/CollectionsMarshalBenchmark/CollectionsMarshalBenchmark.csproj new file mode 100644 index 000000000..3d1ccb4be --- /dev/null +++ b/benchmark/CollectionsMarshalBenchmark/CollectionsMarshalBenchmark.csproj @@ -0,0 +1,22 @@ + + + + Exe + net8.0 + Benchmark + enable + true + + false + + + + + + + + + + + + diff --git a/benchmark/CollectionsMarshalBenchmark/OldListFormatter`1.cs b/benchmark/CollectionsMarshalBenchmark/OldListFormatter`1.cs new file mode 100644 index 000000000..2d706c7fc --- /dev/null +++ b/benchmark/CollectionsMarshalBenchmark/OldListFormatter`1.cs @@ -0,0 +1,67 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Buffers; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; + +namespace MessagePack.Formatters; + +public sealed class OldListFormatter + : IMessagePackFormatter?> +{ + public void Serialize(ref MessagePackWriter writer, List? value, MessagePackSerializerOptions options) + { + if (value == null) + { + writer.WriteNil(); + } + else + { + IMessagePackFormatter formatter = options.Resolver.GetFormatterWithVerify(); + + var c = value.Count; + writer.WriteArrayHeader(c); + for (int i = 0; i < c; i++) + { + writer.CancellationToken.ThrowIfCancellationRequested(); + formatter.Serialize(ref writer, value[i], options); + } + } + } + + public List? Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) + { + if (reader.TryReadNil()) + { + return default; + } + else + { + IMessagePackFormatter formatter = options.Resolver.GetFormatterWithVerify(); + + var len = reader.ReadArrayHeader(); + var list = new List((int)len); + options.Security.DepthStep(ref reader); + try + { + for (int i = 0; i < len; i++) + { + reader.CancellationToken.ThrowIfCancellationRequested(); + list.Add(formatter.Deserialize(ref reader, options)); + } + } + finally + { + reader.Depth--; + } + + return list; + } + } +} diff --git a/benchmark/CollectionsMarshalBenchmark/Program.cs b/benchmark/CollectionsMarshalBenchmark/Program.cs new file mode 100644 index 000000000..ed60f5337 --- /dev/null +++ b/benchmark/CollectionsMarshalBenchmark/Program.cs @@ -0,0 +1,16 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Benchmark; +using BenchmarkDotNet.Running; + +namespace CollectionsMarshalBenchmark; + +internal class Program +{ + private static void Main() + { + BenchmarkRunner.Run(); + BenchmarkRunner.Run(); + } +} diff --git a/benchmark/CollectionsMarshalBenchmark/RandomByteBenchmark.cs b/benchmark/CollectionsMarshalBenchmark/RandomByteBenchmark.cs new file mode 100644 index 000000000..9b16526e0 --- /dev/null +++ b/benchmark/CollectionsMarshalBenchmark/RandomByteBenchmark.cs @@ -0,0 +1,86 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using BenchmarkDotNet.Attributes; +using MessagePack; +using MessagePack.Formatters; + +#pragma warning disable SA1402 // File may only contain a single type +#pragma warning disable SA1649 // File name should match first type name + +namespace Benchmark; + +[MessagePackObject] +public struct OldTypeByte(List value) +{ + public OldTypeByte() + : this([]) + { + } + + [Key(0)] + [MessagePackFormatter(typeof(OldListFormatter))] + public List Value = value; +} + +[MessagePackObject] +public struct NewTypeByte(List value) +{ + public NewTypeByte() + : this([]) + { + } + + [Key(0)] + [MessagePackFormatter(typeof(ListFormatter))] + public List Value = value; +} + +public class RandomByteBenchmark +{ + [Params(1, 64, 1024, 16 * 1024 * 1024)] + public int Size { get; set; } + + private List input = []; + private byte[] serialized = []; + + [GlobalSetup] + public void SetUp() + { + input = new(Size); + for (var i = 0; i < Size; i++) + { + input.Add((byte)Random.Shared.Next()); + } + + serialized = MessagePackSerializer.Serialize(new NewTypeByte(input)); + } + + [Benchmark] + public byte[] SerializeNew() + { + return MessagePackSerializer.Serialize(new NewTypeByte(input)); + } + + [Benchmark] + public byte[] SerializeOld() + { + return MessagePackSerializer.Serialize(new OldTypeByte(input)); + } + + [Benchmark] + public List DeserializeNew() + { + return MessagePackSerializer.Deserialize(serialized).Value; + } + + [Benchmark] + public List DeserializeOld() + { + return MessagePackSerializer.Deserialize(serialized).Value; + } +} diff --git a/benchmark/CollectionsMarshalBenchmark/RandomMatrix4x4Benchmark.cs b/benchmark/CollectionsMarshalBenchmark/RandomMatrix4x4Benchmark.cs new file mode 100644 index 000000000..754637bf9 --- /dev/null +++ b/benchmark/CollectionsMarshalBenchmark/RandomMatrix4x4Benchmark.cs @@ -0,0 +1,128 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using BenchmarkDotNet.Attributes; +using MessagePack; +using MessagePack.Formatters; + +#pragma warning disable SA1402 // File may only contain a single type +#pragma warning disable SA1649 // File name should match first type name + +namespace Benchmark; + +[MessagePack.MessagePackObjectAttribute] +public struct Matrix4x4 +{ + [MessagePack.Key(0)] public float M11; + [MessagePack.Key(1)] public float M12; + [MessagePack.Key(2)] public float M13; + [MessagePack.Key(3)] public float M14; + [MessagePack.Key(4)] public float M21; + [MessagePack.Key(5)] public float M22; + [MessagePack.Key(6)] public float M23; + [MessagePack.Key(7)] public float M24; + [MessagePack.Key(8)] public float M31; + [MessagePack.Key(9)] public float M32; + [MessagePack.Key(10)] public float M33; + [MessagePack.Key(11)] public float M34; + [MessagePack.Key(12)] public float M41; + [MessagePack.Key(13)] public float M42; + [MessagePack.Key(14)] public float M43; + [MessagePack.Key(15)] public float M44; + + public Matrix4x4(float m11, float m12, float m13, float m14, float m21, float m22, float m23, float m24, float m31, float m32, float m33, float m34, float m41, float m42, float m43, float m44) + { + M11 = m11; + M12 = m12; + M13 = m13; + M14 = m14; + M21 = m21; + M22 = m22; + M23 = m23; + M24 = m24; + M31 = m31; + M32 = m32; + M33 = m33; + M34 = m34; + M41 = m41; + M42 = m42; + M43 = m43; + M44 = m44; + } +} + +[MessagePackObject] +public struct OldTypeMatrix4x4(List value) +{ + public OldTypeMatrix4x4() + : this([]) + { + } + + [Key(0)] + [MessagePackFormatter(typeof(OldListFormatter))] + public List Value = value; +} + +[MessagePackObject] +public struct NewTypeMatrix4x4(List value) +{ + public NewTypeMatrix4x4() + : this([]) + { + } + + [Key(0)] + [MessagePackFormatter(typeof(ListFormatter))] + public List Value = value; +} + +public class RandomMatrix4x4Benchmark +{ + [Params(1, 64, 1024, 16 * 1024 * 1024)] + public int Size { get; set; } + + private List input = []; + private byte[] serialized = []; + + [GlobalSetup] + public void SetUp() + { + input = new(Size); + var r = new Random(); + for (var i = 0; i < Size; i++) + { + input.Add(new((float)r.NextDouble(), (float)r.NextDouble(), (float)r.NextDouble(), (float)r.NextDouble(), (float)r.NextDouble(), (float)r.NextDouble(), (float)r.NextDouble(), (float)r.NextDouble(), (float)r.NextDouble(), (float)r.NextDouble(), (float)r.NextDouble(), (float)r.NextDouble(), (float)r.NextDouble(), (float)r.NextDouble(), (float)r.NextDouble(), (float)r.NextDouble())); + } + + serialized = MessagePackSerializer.Serialize(new NewTypeMatrix4x4(input)); + } + + [Benchmark] + public byte[] SerializeNew() + { + return MessagePackSerializer.Serialize(new NewTypeMatrix4x4(input)); + } + + [Benchmark] + public byte[] SerializeOld() + { + return MessagePackSerializer.Serialize(new OldTypeMatrix4x4(input)); + } + + [Benchmark] + public List DeserializeNew() + { + return MessagePackSerializer.Deserialize(serialized).Value; + } + + [Benchmark] + public List DeserializeOld() + { + return MessagePackSerializer.Deserialize(serialized).Value; + } +} diff --git a/benchmark/ExperimentalBenchmark/BoolDeserializeTest.cs b/benchmark/ExperimentalBenchmark/BoolDeserializeTest.cs new file mode 100644 index 000000000..68365844c --- /dev/null +++ b/benchmark/ExperimentalBenchmark/BoolDeserializeTest.cs @@ -0,0 +1,76 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +extern alias e; + +namespace Benchmark; + +public class BoolDeserializeTest +{ +#pragma warning disable SA1117 + [Params( + "0", + "1 true", "1 false", "1 rand", + "3 true", "3 false", "3 rand", + "8 rand", + "16 rand", + "31 rand", + "64 rand", + "4096 rand", + "4194304 rand")] + public string Setting { get; set; } = string.Empty; +#pragma warning restore SA1117 + + private byte[] binary = []; + + [GlobalSetup] + public void SetUp() + { + var span = Setting.AsSpan(); + var firstSpace = span.IndexOf(' '); + var sizeSpan = span; + if (firstSpace >= 0) + { + sizeSpan = sizeSpan[..firstSpace]; + } + + var size = int.Parse(sizeSpan); + var input = size == 0 ? [] : new bool[size]; + if (input.Length != 0) + { + span = span[(firstSpace + 1)..]; + switch (span) + { + case "true": + Array.Fill(input, true); + break; + case "false": + Array.Fill(input, false); + break; + default: + foreach (ref var item in input.AsSpan()) + { + item = (Random.Shared.Next() & 1) == 0; + } + + break; + } + } + + binary = MessagePackSerializer.Serialize(input); + } + + [Benchmark(Baseline = true)] + public bool[]? Old() + { + MessagePackReader reader = new(binary); + return BooleanArrayFormatter.Instance.Deserialize(ref reader, default!); + } + + [Benchmark] + public bool[]? Simd() + { + MessagePackReader reader = new(binary); + return e::MessagePack.Formatters.BooleanArrayFormatter.Instance.Deserialize(ref reader, default!); + } +} diff --git a/benchmark/ExperimentalBenchmark/BoolSerializeTest.cs b/benchmark/ExperimentalBenchmark/BoolSerializeTest.cs new file mode 100644 index 000000000..bdf779971 --- /dev/null +++ b/benchmark/ExperimentalBenchmark/BoolSerializeTest.cs @@ -0,0 +1,82 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +extern alias e; + +namespace Benchmark; + +public class BoolSerializeTest +{ +#pragma warning disable SA1117 + [Params( + "0", + "1 true", "1 false", "1 rand", + "3 true", "3 false", "3 rand", + "8 rand", + "16 rand", + "31 rand", + "64 rand", + "4096 rand", + "4194304 rand")] + public string Setting { get; set; } = string.Empty; +#pragma warning restore SA1117 + + private bool[] input = []; + + [GlobalSetup] + public void SetUp() + { + var span = Setting.AsSpan(); + var firstSpace = span.IndexOf(' '); + var sizeSpan = span; + if (firstSpace >= 0) + { + sizeSpan = sizeSpan[..firstSpace]; + } + + var size = int.Parse(sizeSpan); + input = size == 0 ? [] : new bool[size]; + if (input.Length == 0) + { + return; + } + + span = span[(firstSpace + 1)..]; + switch (span) + { + case "true": + Array.Fill(input, true); + break; + case "false": + Array.Fill(input, false); + break; + default: + foreach (ref var item in input.AsSpan()) + { + item = (Random.Shared.Next() & 1) == 0; + } + + break; + } + } + + [Benchmark(Baseline = true)] + public ReadOnlyMemory Old() + { + ArrayBufferWriter bufferWriter = new(); + MessagePackWriter writer = new(bufferWriter); + BooleanArrayFormatter.Instance.Serialize(ref writer, input, default!); + writer.Flush(); + return bufferWriter.WrittenMemory; + } + + [Benchmark] + public ReadOnlyMemory Simd() + { + ArrayBufferWriter bufferWriter = new(); + MessagePackWriter writer = new(bufferWriter); + e::MessagePack.Formatters.BooleanArrayFormatter.Instance.Serialize(ref writer, input, default!); + writer.Flush(); + return bufferWriter.WrittenMemory; + } +} diff --git a/benchmark/ExperimentalBenchmark/DoubleTest.cs b/benchmark/ExperimentalBenchmark/DoubleTest.cs new file mode 100644 index 000000000..5bede4035 --- /dev/null +++ b/benchmark/ExperimentalBenchmark/DoubleTest.cs @@ -0,0 +1,44 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +extern alias e; + +namespace Benchmark; + +public class DoubleTest +{ + [Params(0, 1, 3, 8, 31, 64, 1024, 16777216)] + public int Size { get; set; } + + private double[] input = []; + + [GlobalSetup] + public void SetUp() + { + input = new double[Size]; + for (var i = 0; i < input.Length; i++) + { + input[i] = Random.Shared.NextDouble(); + } + } + + [Benchmark(Baseline = true)] + public ReadOnlyMemory Old() + { + ArrayBufferWriter bufferWriter = new(); + MessagePackWriter writer = new(bufferWriter); + DoubleArrayFormatter.Instance.Serialize(ref writer, input, default!); + writer.Flush(); + return bufferWriter.WrittenMemory; + } + + [Benchmark] + public ReadOnlyMemory Simd() + { + ArrayBufferWriter bufferWriter = new(); + MessagePackWriter writer = new(bufferWriter); + e::MessagePack.Formatters.DoubleArrayFormatter.Instance.Serialize(ref writer, input, default!); + writer.Flush(); + return bufferWriter.WrittenMemory; + } +} diff --git a/benchmark/ExperimentalBenchmark/ExperimentalBenchmark.csproj b/benchmark/ExperimentalBenchmark/ExperimentalBenchmark.csproj index 08cce028b..2bd081277 100644 --- a/benchmark/ExperimentalBenchmark/ExperimentalBenchmark.csproj +++ b/benchmark/ExperimentalBenchmark/ExperimentalBenchmark.csproj @@ -1,11 +1,12 @@ - + Exe - net6.0 + net8.0 Benchmark true $(NoWarn);MSB3243 + enable @@ -15,23 +16,19 @@ - newmsgpack - - - newmsgpack - - - newmsgpack + e + + - - MessagePack_2_1_165.dll - oldmsgpack - true - false - + + + + + + diff --git a/benchmark/ExperimentalBenchmark/IntTest.cs b/benchmark/ExperimentalBenchmark/IntTest.cs new file mode 100644 index 000000000..4b21f08d7 --- /dev/null +++ b/benchmark/ExperimentalBenchmark/IntTest.cs @@ -0,0 +1,75 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +extern alias e; + +namespace Benchmark; + +public class IntTest +{ +#pragma warning disable SA1117 + [Params( + "0", + "1 rand", "1 0", + "3 rand", "3 0", + "8 rand", "8 0", + "16 rand", "16 0", + "31 rand", "31 0", + "64 rand", "64 0", + "4096 rand", "4096 0", + "4194304 rand", "4194304 0")] + public string Setting { get; set; } = string.Empty; +#pragma warning restore SA1117 + + private int[] input = []; + + [GlobalSetup] + public void SetUp() + { + var span = Setting.AsSpan(); + var firstSpace = span.IndexOf(' '); + var sizeSpan = span; + if (firstSpace >= 0) + { + sizeSpan = sizeSpan[..firstSpace]; + } + + var size = int.Parse(sizeSpan); + input = size == 0 ? [] : new int[size]; + if (input.Length == 0) + { + return; + } + + span = span[(firstSpace + 1)..]; + switch (span) + { + case "rand": + Random.Shared.NextBytes(MemoryMarshal.AsBytes(input.AsSpan())); + break; + default: + Array.Fill(input, int.Parse(span)); + break; + } + } + + [Benchmark(Baseline = true)] + public ReadOnlyMemory Old() + { + ArrayBufferWriter bufferWriter = new(); + MessagePackWriter writer = new(bufferWriter); + Int32ArrayFormatter.Instance.Serialize(ref writer, input, default!); + writer.Flush(); + return bufferWriter.WrittenMemory; + } + + [Benchmark] + public ReadOnlyMemory Simd() + { + ArrayBufferWriter bufferWriter = new(); + MessagePackWriter writer = new(bufferWriter); + e::MessagePack.Formatters.Int32ArrayFormatter.Instance.Serialize(ref writer, input, default!); + writer.Flush(); + return bufferWriter.WrittenMemory; + } +} diff --git a/benchmark/ExperimentalBenchmark/MessagePack_2_1_165.dll b/benchmark/ExperimentalBenchmark/MessagePack_2_1_165.dll deleted file mode 100644 index 7dabd586c..000000000 Binary files a/benchmark/ExperimentalBenchmark/MessagePack_2_1_165.dll and /dev/null differ diff --git a/benchmark/ExperimentalBenchmark/Program.cs b/benchmark/ExperimentalBenchmark/Program.cs index 6e3e18511..7e667e8ad 100644 --- a/benchmark/ExperimentalBenchmark/Program.cs +++ b/benchmark/ExperimentalBenchmark/Program.cs @@ -1,22 +1,41 @@ // Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using Benchmark; +#if NET7_0_OR_GREATER +using System.Runtime.Intrinsics; +#else +using System.Runtime.Intrinsics.Arm; +using System.Runtime.Intrinsics.X86; +#endif +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Running; -namespace HardwareIntrinsicsBenchmark +namespace Benchmark; + +internal class Program { - internal class Program + private static void Main(string[] args) { - private static void Main() + var noDynamicPGO = new EnvironmentVariable("DOTNET_TieredPGO", "0"); + IConfig config = DefaultConfig.Instance + .HideColumns(Column.EnvironmentVariables, Column.RatioSD, Column.Error) + .AddJob(Job.Default.WithEnvironmentVariables([ + new("DOTNET_EnableHWIntrinsic", "0"), + noDynamicPGO + ]).WithId("Scalar").AsBaseline()); + +#if NET7_0_OR_GREATER + if (Vector128.IsHardwareAccelerated) +#else + if (Sse42.IsSupported || AdvSimd.IsSupported) +#endif { - BenchmarkRunner.Run(); - BenchmarkRunner.Run(); - BenchmarkRunner.Run(); - BenchmarkRunner.Run(); - BenchmarkRunner.Run(); - BenchmarkRunner.Run(); - BenchmarkRunner.Run(); + config = config.AddJob(Job.Default.WithEnvironmentVariable(noDynamicPGO).WithId("Vector")); } + + BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly) + .Run(args, config); } } diff --git a/benchmark/ExperimentalBenchmark/SByteTest.cs b/benchmark/ExperimentalBenchmark/SByteTest.cs new file mode 100644 index 000000000..4d60f6fad --- /dev/null +++ b/benchmark/ExperimentalBenchmark/SByteTest.cs @@ -0,0 +1,75 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +extern alias e; + +namespace Benchmark; + +public class SByteTest +{ +#pragma warning disable SA1117 + [Params( + "0", + "1 rand", "1 127", "1 -35", + "3 rand", "3 127", "3 -35", + "8 rand", "8 127", "8 -35", + "16 rand", "16 127", "16 -35", + "31 rand", "31 127", "31 -35", + "64 rand", "64 127", "64 -35", + "4096 rand", "4096 127", "4096 -35", + "4194304 rand", "4194304 127", "4194304 -35")] + public string Setting { get; set; } = string.Empty; +#pragma warning restore SA1117 + + private sbyte[] input = []; + + [GlobalSetup] + public void SetUp() + { + var span = Setting.AsSpan(); + var firstSpace = span.IndexOf(' '); + var sizeSpan = span; + if (firstSpace >= 0) + { + sizeSpan = sizeSpan[..firstSpace]; + } + + var size = int.Parse(sizeSpan); + input = size == 0 ? [] : new sbyte[size]; + if (input.Length == 0) + { + return; + } + + span = span[(firstSpace + 1)..]; + switch (span) + { + case "rand": + Random.Shared.NextBytes(MemoryMarshal.AsBytes(input.AsSpan())); + break; + default: + Array.Fill(input, sbyte.Parse(span)); + break; + } + } + + [Benchmark(Baseline = true)] + public ReadOnlyMemory Old() + { + ArrayBufferWriter bufferWriter = new(); + MessagePackWriter writer = new(bufferWriter); + SByteArrayFormatter.Instance.Serialize(ref writer, input, default!); + writer.Flush(); + return bufferWriter.WrittenMemory; + } + + [Benchmark] + public ReadOnlyMemory Simd() + { + ArrayBufferWriter bufferWriter = new(); + MessagePackWriter writer = new(bufferWriter); + e::MessagePack.Formatters.SByteArrayFormatter.Instance.Serialize(ref writer, input, default!); + writer.Flush(); + return bufferWriter.WrittenMemory; + } +} diff --git a/benchmark/ExperimentalBenchmark/ShortTest.cs b/benchmark/ExperimentalBenchmark/ShortTest.cs new file mode 100644 index 000000000..4b2fa8300 --- /dev/null +++ b/benchmark/ExperimentalBenchmark/ShortTest.cs @@ -0,0 +1,75 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +extern alias e; + +namespace Benchmark; + +public class ShortTest +{ +#pragma warning disable SA1117 + [Params( + "0", + "1 rand", "1 127", "1 -135", + "3 rand", "3 127", "3 -135", + "8 rand", "8 127", "8 -135", + "16 rand", "16 127", "16 -135", + "31 rand", "31 127", "31 -135", + "64 rand", "64 127", "64 -135", + "4096 rand", "4096 127", "4096 -135", + "4194304 rand", "4194304 127", "4194304 -135")] + public string Setting { get; set; } = string.Empty; +#pragma warning restore SA1117 + + private short[] input = []; + + [GlobalSetup] + public void SetUp() + { + var span = Setting.AsSpan(); + var firstSpace = span.IndexOf(' '); + var sizeSpan = span; + if (firstSpace >= 0) + { + sizeSpan = sizeSpan[..firstSpace]; + } + + var size = int.Parse(sizeSpan); + input = size == 0 ? [] : new short[size]; + if (input.Length == 0) + { + return; + } + + span = span[(firstSpace + 1)..]; + switch (span) + { + case "rand": + Random.Shared.NextBytes(MemoryMarshal.AsBytes(input.AsSpan())); + break; + default: + Array.Fill(input, short.Parse(span)); + break; + } + } + + [Benchmark(Baseline = true)] + public ReadOnlyMemory Old() + { + ArrayBufferWriter bufferWriter = new(); + MessagePackWriter writer = new(bufferWriter); + Int16ArrayFormatter.Instance.Serialize(ref writer, input, default!); + writer.Flush(); + return bufferWriter.WrittenMemory; + } + + [Benchmark] + public ReadOnlyMemory Simd() + { + ArrayBufferWriter bufferWriter = new(); + MessagePackWriter writer = new(bufferWriter); + e::MessagePack.Formatters.Int16ArrayFormatter.Instance.Serialize(ref writer, input, default!); + writer.Flush(); + return bufferWriter.WrittenMemory; + } +} diff --git a/benchmark/ExperimentalBenchmark/SingleTest.cs b/benchmark/ExperimentalBenchmark/SingleTest.cs new file mode 100644 index 000000000..094e111c3 --- /dev/null +++ b/benchmark/ExperimentalBenchmark/SingleTest.cs @@ -0,0 +1,44 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +extern alias e; + +namespace Benchmark; + +public class SingleTest +{ + [Params(0, 1, 3, 4, 8, 64, 1024, 16 * 1024 * 1024)] + public int Size { get; set; } + + private float[] input = []; + + [GlobalSetup] + public void SetUp() + { + input = new float[Size]; + for (var i = 0; i < input.Length; i++) + { + input[i] = Random.Shared.NextSingle(); + } + } + + [Benchmark(Baseline = true)] + public ReadOnlyMemory Old() + { + ArrayBufferWriter bufferWriter = new(); + MessagePackWriter writer = new(bufferWriter); + SingleArrayFormatter.Instance.Serialize(ref writer, input, default!); + writer.Flush(); + return bufferWriter.WrittenMemory; + } + + [Benchmark] + public ReadOnlyMemory Simd() + { + ArrayBufferWriter bufferWriter = new(); + MessagePackWriter writer = new(bufferWriter); + e::MessagePack.Formatters.SingleArrayFormatter.Instance.Serialize(ref writer, input, default!); + writer.Flush(); + return bufferWriter.WrittenMemory; + } +} diff --git a/benchmark/ExperimentalBenchmark/Tests.cs b/benchmark/ExperimentalBenchmark/Tests.cs deleted file mode 100644 index e51837b1b..000000000 --- a/benchmark/ExperimentalBenchmark/Tests.cs +++ /dev/null @@ -1,487 +0,0 @@ -// Copyright (c) All contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -extern alias oldmsgpack; -extern alias newmsgpack; - -using System; -using System.Linq; -using System.Runtime.InteropServices; -using BenchmarkDotNet.Attributes; - -#pragma warning disable SA1402 // File may only contain a single type -#pragma warning disable SA1649 // File name should match first type name - -namespace Benchmark -{ - [newmsgpack::MessagePack.MessagePackObjectAttribute] - public struct Matrix4x4 - { - [newmsgpack::MessagePack.Key(0)] public float M11; - [newmsgpack::MessagePack.Key(1)] public float M12; - [newmsgpack::MessagePack.Key(2)] public float M13; - [newmsgpack::MessagePack.Key(3)] public float M14; - [newmsgpack::MessagePack.Key(4)] public float M21; - [newmsgpack::MessagePack.Key(5)] public float M22; - [newmsgpack::MessagePack.Key(6)] public float M23; - [newmsgpack::MessagePack.Key(7)] public float M24; - [newmsgpack::MessagePack.Key(8)] public float M31; - [newmsgpack::MessagePack.Key(9)] public float M32; - [newmsgpack::MessagePack.Key(10)] public float M33; - [newmsgpack::MessagePack.Key(11)] public float M34; - [newmsgpack::MessagePack.Key(12)] public float M41; - [newmsgpack::MessagePack.Key(13)] public float M42; - [newmsgpack::MessagePack.Key(14)] public float M43; - [newmsgpack::MessagePack.Key(15)] public float M44; - - public Matrix4x4(float m11, float m12, float m13, float m14, float m21, float m22, float m23, float m24, float m31, float m32, float m33, float m34, float m41, float m42, float m43, float m44) - { - M11 = m11; - M12 = m12; - M13 = m13; - M14 = m14; - M21 = m21; - M22 = m22; - M23 = m23; - M24 = m24; - M31 = m31; - M32 = m32; - M33 = m33; - M34 = m34; - M41 = m41; - M42 = m42; - M43 = m43; - M44 = m44; - } - } - - [ShortRunJob] - public class UnsafeUnmanagedStructArrayBenchmark - { - [Params(1, 64, 1024, 16 * 1024 * 1024)] - public int Size { get; set; } - - private Matrix4x4[] input; - private byte[] inputSerializedUnsafe; - private byte[] inputSerializedNormal; - - private newmsgpack::MessagePack.MessagePackSerializerOptions options; - - [GlobalSetup] - public void SetUp() - { - var resolver = newmsgpack::MessagePack.Resolvers.CompositeResolver.Create(new newmsgpack::MessagePack.Formatters.IMessagePackFormatter[] { new newmsgpack::MessagePack.Formatters.UnsafeUnmanagedStructArrayFormatter(50) }, new[] { newmsgpack::MessagePack.Resolvers.StandardResolver.Instance }); - options = newmsgpack::MessagePack.MessagePackSerializerOptions.Standard.WithResolver(resolver); - - input = new Matrix4x4[Size]; - var r = new Random(); - for (var i = 0; i < input.Length; i++) - { - input[i] = new Matrix4x4((float)r.NextDouble(), (float)r.NextDouble(), (float)r.NextDouble(), (float)r.NextDouble(), (float)r.NextDouble(), (float)r.NextDouble(), (float)r.NextDouble(), (float)r.NextDouble(), (float)r.NextDouble(), (float)r.NextDouble(), (float)r.NextDouble(), (float)r.NextDouble(), (float)r.NextDouble(), (float)r.NextDouble(), (float)r.NextDouble(), (float)r.NextDouble()); - } - - inputSerializedUnsafe = newmsgpack::MessagePack.MessagePackSerializer.Serialize(input, options); - inputSerializedNormal = newmsgpack::MessagePack.MessagePackSerializer.Serialize(input); - } - - [Benchmark] - public byte[] SerializeUnsafe() - { - return newmsgpack::MessagePack.MessagePackSerializer.Serialize(input, options); - } - - [Benchmark] - public byte[] SerializeNormal() - { - return newmsgpack::MessagePack.MessagePackSerializer.Serialize(input); - } - - [Benchmark] - public Matrix4x4[] DeserializeUnsafe() - { - return newmsgpack::MessagePack.MessagePackSerializer.Deserialize(inputSerializedUnsafe, options); - } - - [Benchmark] - public Matrix4x4[] DeserializeNormal() - { - return newmsgpack::MessagePack.MessagePackSerializer.Deserialize(inputSerializedNormal); - } - } - - [ShortRunJob] - public class BooleanArrayBenchmarkMessagePackNoSingleInstructionMultipleDataVsMessagePackSingleInstructionMultipleData - { - [Params(64, 1024, 16 * 1024 * 1024)] - public int Size { get; set; } - - private bool[] input; - private byte[] inputSerialized; - private bool[] inputTrue; - private bool[] inputFalse; - private newmsgpack::MessagePack.MessagePackSerializerOptions options; - - [GlobalSetup] - public void SetUp() - { - var resolver = newmsgpack::MessagePack.Resolvers.CompositeResolver.Create(newmsgpack::MessagePack.Resolvers.PrimitiveArrayResolver.Instance, newmsgpack::MessagePack.Resolvers.StandardResolver.Instance); - options = newmsgpack::MessagePack.MessagePackSerializerOptions.Standard.WithResolver(resolver); - - inputFalse = new bool[Size]; - inputTrue = new bool[Size]; - input = new bool[Size]; - - var r = new Random(); - for (var i = 0; i < inputTrue.Length; i++) - { - inputTrue[i] = true; - input[i] = r.Next(0, 2) == 0; - } - - inputSerialized = newmsgpack::MessagePack.MessagePackSerializer.Serialize(input, options); - - if (!oldmsgpack::MessagePack.MessagePackSerializer.Serialize(input).SequenceEqual(inputSerialized)) - { - throw new InvalidProgramException(); - } - } - - [Benchmark] - public byte[] SerializeSingleInstructionMultipleData() - { - return newmsgpack::MessagePack.MessagePackSerializer.Serialize(input, options); - } - - [Benchmark] - public byte[] SerializeNoSingleInstructionMultipleData() - { - return oldmsgpack::MessagePack.MessagePackSerializer.Serialize(input); - } - - [Benchmark] - public bool[] DeSerializeSingleInstructionMultipleData() - { - return newmsgpack::MessagePack.MessagePackSerializer.Deserialize(inputSerialized, options); - } - - [Benchmark] - public bool[] DeserializeNoSingleInstructionMultipleData() - { - return oldmsgpack::MessagePack.MessagePackSerializer.Deserialize(inputSerialized); - } - - [Benchmark] - public byte[] SerializeSingleInstructionMultipleDataFalse() - { - return newmsgpack::MessagePack.MessagePackSerializer.Serialize(inputFalse, options); - } - - [Benchmark] - public byte[] SerializeNoSingleInstructionMultipleDataFalse() - { - return oldmsgpack::MessagePack.MessagePackSerializer.Serialize(inputFalse); - } - - [Benchmark] - public byte[] SerializeSingleInstructionMultipleDataTrue() - { - return newmsgpack::MessagePack.MessagePackSerializer.Serialize(inputTrue, options); - } - - [Benchmark] - public byte[] SerializeNoSingleInstructionMultipleDataTrue() - { - return oldmsgpack::MessagePack.MessagePackSerializer.Serialize(inputTrue); - } - } - - [ShortRunJob] - public class Int8ArrayBenchmarkMessagePackNoSingleInstructionMultipleDataVsMessagePackSingleInstructionMultipleData - { - [Params(64, 1024, 16 * 1024 * 1024)] - public int Size { get; set; } - - private sbyte[] input; - private sbyte[] inputM32; - private sbyte[] inputM33; - private sbyte[] zero; - private newmsgpack::MessagePack.MessagePackSerializerOptions options; - - [GlobalSetup] - public void SetUp() - { - var resolver = newmsgpack::MessagePack.Resolvers.CompositeResolver.Create(newmsgpack::MessagePack.Resolvers.PrimitiveArrayResolver.Instance, newmsgpack::MessagePack.Resolvers.StandardResolver.Instance); - options = newmsgpack::MessagePack.MessagePackSerializerOptions.Standard.WithResolver(resolver); - - zero = new sbyte[Size]; - inputM33 = new sbyte[Size]; - inputM32 = new sbyte[Size]; - input = new sbyte[Size]; - - var r = new Random(); - r.NextBytes(MemoryMarshal.AsBytes(input.AsSpan())); - for (var i = 0; i < inputM32.Length; i++) - { - inputM32[i] = -32; - inputM33[i] = -33; - } - - if (!oldmsgpack::MessagePack.MessagePackSerializer.Serialize(input).SequenceEqual(newmsgpack::MessagePack.MessagePackSerializer.Serialize(input, options))) - { - throw new InvalidProgramException(); - } - } - - [Benchmark] - public byte[] SerializeSingleInstructionMultipleData() - { - return newmsgpack::MessagePack.MessagePackSerializer.Serialize(input, options); - } - - [Benchmark] - public byte[] SerializeNoSingleInstructionMultipleData() - { - return oldmsgpack::MessagePack.MessagePackSerializer.Serialize(input); - } - - [Benchmark] - public byte[] SerializeSingleInstructionMultipleDataZero() - { - return newmsgpack::MessagePack.MessagePackSerializer.Serialize(zero, options); - } - - [Benchmark] - public byte[] SerializeNoSingleInstructionMultipleDataZero() - { - return oldmsgpack::MessagePack.MessagePackSerializer.Serialize(zero); - } - - [Benchmark] - public byte[] SerializeSingleInstructionMultipleDataM32() - { - return newmsgpack::MessagePack.MessagePackSerializer.Serialize(inputM32, options); - } - - [Benchmark] - public byte[] SerializeNoSingleInstructionMultipleDataM32() - { - return oldmsgpack::MessagePack.MessagePackSerializer.Serialize(inputM32); - } - - [Benchmark] - public byte[] SerializeSingleInstructionMultipleDataM33() - { - return newmsgpack::MessagePack.MessagePackSerializer.Serialize(inputM33, options); - } - - [Benchmark] - public byte[] SerializeNoSingleInstructionMultipleDataM33() - { - return oldmsgpack::MessagePack.MessagePackSerializer.Serialize(inputM33); - } - } - - [ShortRunJob] - public class Int16ArrayBenchmarkMessagePackNoSingleInstructionMultipleDataVsMessagePackSingleInstructionMultipleData - { - [Params(16, 1024, 16 * 1024 * 1024)] - public int Size { get; set; } - - private newmsgpack::MessagePack.MessagePackSerializerOptions options; - private short[] input; - private short[] zero; - - [GlobalSetup] - public void SetUp() - { - var resolver = newmsgpack::MessagePack.Resolvers.CompositeResolver.Create(newmsgpack::MessagePack.Resolvers.PrimitiveArrayResolver.Instance, newmsgpack::MessagePack.Resolvers.StandardResolver.Instance); - options = newmsgpack::MessagePack.MessagePackSerializerOptions.Standard.WithResolver(resolver); - - input = new short[Size]; - zero = new short[Size]; - var r = new Random(); - r.NextBytes(MemoryMarshal.AsBytes(input.AsSpan())); - - if (!oldmsgpack::MessagePack.MessagePackSerializer.Serialize(input).SequenceEqual(newmsgpack::MessagePack.MessagePackSerializer.Serialize(input, options))) - { - throw new InvalidProgramException(); - } - } - - [Benchmark] - public byte[] SerializeSingleInstructionMultipleData() - { - return newmsgpack::MessagePack.MessagePackSerializer.Serialize(input, options); - } - - [Benchmark] - public byte[] SerializeNoSingleInstructionMultipleData() - { - return oldmsgpack::MessagePack.MessagePackSerializer.Serialize(input); - } - - [Benchmark] - public byte[] SerializeSingleInstructionMultipleDataZero() - { - return newmsgpack::MessagePack.MessagePackSerializer.Serialize(zero, options); - } - - [Benchmark] - public byte[] SerializeNoSingleInstructionMultipleDataZero() - { - return oldmsgpack::MessagePack.MessagePackSerializer.Serialize(zero); - } - } - - [ShortRunJob] - public class Int32ArrayBenchmarkMessagePackNoSingleInstructionMultipleDataVsMessagePackSingleInstructionMultipleData - { - [Params(8, 1024, 16 * 1024 * 1024)] - public int Size { get; set; } - - private newmsgpack::MessagePack.MessagePackSerializerOptions options; - private int[] input; - private int[] zero; - private int[] inputShortMin; - - [GlobalSetup] - public void SetUp() - { - var resolver = newmsgpack::MessagePack.Resolvers.CompositeResolver.Create(newmsgpack::MessagePack.Resolvers.PrimitiveArrayResolver.Instance, newmsgpack::MessagePack.Resolvers.StandardResolver.Instance); - options = newmsgpack::MessagePack.MessagePackSerializerOptions.Standard.WithResolver(resolver); - - input = new int[Size]; - zero = new int[Size]; - inputShortMin = new int[Size]; - var r = new Random(); - r.NextBytes(MemoryMarshal.AsBytes(input.AsSpan())); - for (var i = 0; i < inputShortMin.Length; i++) - { - inputShortMin[i] = short.MinValue; - } - - if (!oldmsgpack::MessagePack.MessagePackSerializer.Serialize(input).SequenceEqual(newmsgpack::MessagePack.MessagePackSerializer.Serialize(input, options))) - { - throw new InvalidProgramException(); - } - } - - [Benchmark] - public byte[] SerializeSingleInstructionMultipleData() - { - return newmsgpack::MessagePack.MessagePackSerializer.Serialize(input, options); - } - - [Benchmark] - public byte[] SerializeNoSingleInstructionMultipleData() - { - return oldmsgpack::MessagePack.MessagePackSerializer.Serialize(input); - } - - [Benchmark] - public byte[] SerializeSingleInstructionMultipleDataZero() - { - return newmsgpack::MessagePack.MessagePackSerializer.Serialize(zero, options); - } - - [Benchmark] - public byte[] SerializeNoSingleInstructionMultipleDataZero() - { - return oldmsgpack::MessagePack.MessagePackSerializer.Serialize(zero); - } - - [Benchmark] - public byte[] SerializeSingleInstructionMultipleDataShortMin() - { - return newmsgpack::MessagePack.MessagePackSerializer.Serialize(inputShortMin, options); - } - - [Benchmark] - public byte[] SerializeNoSingleInstructionMultipleDataShortMin() - { - return oldmsgpack::MessagePack.MessagePackSerializer.Serialize(inputShortMin); - } - } - - [ShortRunJob] - public class SingleArrayBenchmarkMessagePackNoSingleInstructionMultipleDataVsMessagePackSingleInstructionMultipleData - { - [Params(64, 1024, 16 * 1024 * 1024)] - public int Size { get; set; } - - private newmsgpack::MessagePack.MessagePackSerializerOptions options; - private float[] input; - - [GlobalSetup] - public void SetUp() - { - var resolver = newmsgpack::MessagePack.Resolvers.CompositeResolver.Create(newmsgpack::MessagePack.Resolvers.PrimitiveArrayResolver.Instance, newmsgpack::MessagePack.Resolvers.StandardResolver.Instance); - options = newmsgpack::MessagePack.MessagePackSerializerOptions.Standard.WithResolver(resolver); - input = new float[Size]; - - var r = new Random(); - for (var i = 0; i < input.Length; i++) - { - input[i] = (float)r.NextDouble(); - } - - if (!oldmsgpack::MessagePack.MessagePackSerializer.Serialize(input).SequenceEqual(newmsgpack::MessagePack.MessagePackSerializer.Serialize(input, options))) - { - throw new InvalidProgramException(); - } - } - - [Benchmark] - public byte[] SerializeSingleInstructionMultipleData() - { - return newmsgpack::MessagePack.MessagePackSerializer.Serialize(input, options); - } - - [Benchmark] - public byte[] SerializeNoSingleInstructionMultipleData() - { - return oldmsgpack::MessagePack.MessagePackSerializer.Serialize(input); - } - } - - [ShortRunJob] - public class DoubleArrayBenchmarkMessagePackNoSingleInstructionMultipleDataVsMessagePackSingleInstructionMultipleData - { - [Params(64, 1024, 16 * 1024 * 1024)] - public int Size { get; set; } - - private newmsgpack::MessagePack.MessagePackSerializerOptions options; - private double[] input; - - [GlobalSetup] - public void SetUp() - { - var resolver = newmsgpack::MessagePack.Resolvers.CompositeResolver.Create(newmsgpack::MessagePack.Resolvers.PrimitiveArrayResolver.Instance, newmsgpack::MessagePack.Resolvers.StandardResolver.Instance); - options = newmsgpack::MessagePack.MessagePackSerializerOptions.Standard.WithResolver(resolver); - input = new double[Size]; - - var r = new Random(); - for (var i = 0; i < input.Length; i++) - { - input[i] = r.NextDouble(); - } - - if (!oldmsgpack::MessagePack.MessagePackSerializer.Serialize(input).SequenceEqual(newmsgpack::MessagePack.MessagePackSerializer.Serialize(input, options))) - { - throw new InvalidProgramException(); - } - } - - [Benchmark] - public byte[] SerializeSingleInstructionMultipleData() - { - return newmsgpack::MessagePack.MessagePackSerializer.Serialize(input, options); - } - - [Benchmark] - public byte[] SerializeNoSingleInstructionMultipleData() - { - return oldmsgpack::MessagePack.MessagePackSerializer.Serialize(input); - } - } -} diff --git a/benchmark/SerializerBenchmark/BenchmarkConfig.cs b/benchmark/SerializerBenchmark/BenchmarkConfig.cs index 750d4f2d2..987a1a770 100644 --- a/benchmark/SerializerBenchmark/BenchmarkConfig.cs +++ b/benchmark/SerializerBenchmark/BenchmarkConfig.cs @@ -16,6 +16,7 @@ using BenchmarkDotNet.Order; using BenchmarkDotNet.Reports; using BenchmarkDotNet.Running; +using Perfolizer.Metrology; namespace Benchmark { @@ -27,7 +28,7 @@ public BenchmarkConfig() Job baseConfig = Job.ShortRun.WithIterationCount(1).WithWarmupCount(1); // Add(baseConfig.With(Runtime.Clr).With(Jit.RyuJit).With(Platform.X64)); - this.AddJob(baseConfig.WithRuntime(CoreRuntime.Core31).WithJit(Jit.RyuJit).WithPlatform(Platform.X64)); + this.AddJob(baseConfig.WithEnvironmentVariable(new("DOTNET_TieredPGO", "0")).WithRuntime(CoreRuntime.Core80).WithJit(Jit.RyuJit).WithPlatform(Platform.X64)); this.AddExporter(MarkdownExporter.GitHub); this.AddExporter(CsvExporter.Default); @@ -110,7 +111,7 @@ public string GetValue(Summary summary, BenchmarkCase benchmarkCase, SummaryStyl var cultureInfo = summary.GetCultureInfo(); if (style.PrintUnitsInContent) { - return SizeValue.FromBytes(byteSize).ToString(style.SizeUnit, cultureInfo); + return SizeValue.FromBytes(byteSize).ToString(style.SizeUnit, null, cultureInfo); } return byteSize.ToString("0.##", cultureInfo); diff --git a/benchmark/SerializerBenchmark/Models/AccessToken.cs b/benchmark/SerializerBenchmark/Models/AccessToken.cs index a4884aabe..e5d90923a 100644 --- a/benchmark/SerializerBenchmark/Models/AccessToken.cs +++ b/benchmark/SerializerBenchmark/Models/AccessToken.cs @@ -1,8 +1,8 @@ // Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -extern alias oldmsgpack; extern alias newmsgpack; +extern alias oldmsgpack; using System; using System.Collections.Generic; diff --git a/benchmark/SerializerBenchmark/Models/AccountMerge.cs b/benchmark/SerializerBenchmark/Models/AccountMerge.cs index e3ab86292..e209bf133 100644 --- a/benchmark/SerializerBenchmark/Models/AccountMerge.cs +++ b/benchmark/SerializerBenchmark/Models/AccountMerge.cs @@ -1,8 +1,8 @@ // Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -extern alias oldmsgpack; extern alias newmsgpack; +extern alias oldmsgpack; using System; using ProtoBuf; diff --git a/benchmark/SerializerBenchmark/Models/Answer.cs b/benchmark/SerializerBenchmark/Models/Answer.cs index 442fe8526..56979a7b3 100644 --- a/benchmark/SerializerBenchmark/Models/Answer.cs +++ b/benchmark/SerializerBenchmark/Models/Answer.cs @@ -5,9 +5,8 @@ #pragma warning disable IDE1006 #pragma warning disable SA1516 -extern alias oldmsgpack; extern alias newmsgpack; - +extern alias oldmsgpack; using System; using System.Collections.Generic; using ProtoBuf; diff --git a/benchmark/SerializerBenchmark/Models/Badge.cs b/benchmark/SerializerBenchmark/Models/Badge.cs index fbe6d9d5d..a2f793b5e 100644 --- a/benchmark/SerializerBenchmark/Models/Badge.cs +++ b/benchmark/SerializerBenchmark/Models/Badge.cs @@ -1,8 +1,8 @@ // Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -extern alias oldmsgpack; extern alias newmsgpack; +extern alias oldmsgpack; using ProtoBuf; diff --git a/benchmark/SerializerBenchmark/Models/Comment.cs b/benchmark/SerializerBenchmark/Models/Comment.cs index e923671df..eb0dc9767 100644 --- a/benchmark/SerializerBenchmark/Models/Comment.cs +++ b/benchmark/SerializerBenchmark/Models/Comment.cs @@ -5,9 +5,8 @@ #pragma warning disable IDE1006 #pragma warning disable SA1516 -extern alias oldmsgpack; extern alias newmsgpack; - +extern alias oldmsgpack; using System; using ProtoBuf; diff --git a/benchmark/SerializerBenchmark/Models/Error.cs b/benchmark/SerializerBenchmark/Models/Error.cs index 1137cd9da..73e73c6dc 100644 --- a/benchmark/SerializerBenchmark/Models/Error.cs +++ b/benchmark/SerializerBenchmark/Models/Error.cs @@ -1,8 +1,8 @@ // Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -extern alias oldmsgpack; extern alias newmsgpack; +extern alias oldmsgpack; using ProtoBuf; diff --git a/benchmark/SerializerBenchmark/Models/Event.cs b/benchmark/SerializerBenchmark/Models/Event.cs index 570e08ad8..5169698a7 100644 --- a/benchmark/SerializerBenchmark/Models/Event.cs +++ b/benchmark/SerializerBenchmark/Models/Event.cs @@ -1,8 +1,8 @@ // Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -extern alias oldmsgpack; extern alias newmsgpack; +extern alias oldmsgpack; using System; using ProtoBuf; diff --git a/benchmark/SerializerBenchmark/Models/Feed.cs b/benchmark/SerializerBenchmark/Models/Feed.cs index 196b2a4c8..d27274e2b 100644 --- a/benchmark/SerializerBenchmark/Models/Feed.cs +++ b/benchmark/SerializerBenchmark/Models/Feed.cs @@ -1,8 +1,8 @@ // Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -extern alias oldmsgpack; extern alias newmsgpack; +extern alias oldmsgpack; using System.Collections.Generic; using ProtoBuf; diff --git a/benchmark/SerializerBenchmark/Models/FlagOption.cs b/benchmark/SerializerBenchmark/Models/FlagOption.cs index 95328086f..0bdb24fce 100644 --- a/benchmark/SerializerBenchmark/Models/FlagOption.cs +++ b/benchmark/SerializerBenchmark/Models/FlagOption.cs @@ -1,8 +1,8 @@ // Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -extern alias oldmsgpack; extern alias newmsgpack; +extern alias oldmsgpack; using System.Collections.Generic; using ProtoBuf; diff --git a/benchmark/SerializerBenchmark/Models/InboxItem.cs b/benchmark/SerializerBenchmark/Models/InboxItem.cs index 30d33a8ef..aaa6e5e2f 100644 --- a/benchmark/SerializerBenchmark/Models/InboxItem.cs +++ b/benchmark/SerializerBenchmark/Models/InboxItem.cs @@ -1,8 +1,8 @@ // Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -extern alias oldmsgpack; extern alias newmsgpack; +extern alias oldmsgpack; using System; using ProtoBuf; diff --git a/benchmark/SerializerBenchmark/Models/Info.cs b/benchmark/SerializerBenchmark/Models/Info.cs index 26662d0e9..dc46daaf8 100644 --- a/benchmark/SerializerBenchmark/Models/Info.cs +++ b/benchmark/SerializerBenchmark/Models/Info.cs @@ -1,8 +1,8 @@ // Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -extern alias oldmsgpack; extern alias newmsgpack; +extern alias oldmsgpack; using System; using System.Collections.Generic; diff --git a/benchmark/SerializerBenchmark/Models/NetworkUser.cs b/benchmark/SerializerBenchmark/Models/NetworkUser.cs index 76a7fd9be..7c0b6682e 100644 --- a/benchmark/SerializerBenchmark/Models/NetworkUser.cs +++ b/benchmark/SerializerBenchmark/Models/NetworkUser.cs @@ -1,8 +1,8 @@ // Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -extern alias oldmsgpack; extern alias newmsgpack; +extern alias oldmsgpack; using System; using ProtoBuf; diff --git a/benchmark/SerializerBenchmark/Models/Notification.cs b/benchmark/SerializerBenchmark/Models/Notification.cs index 677cd3952..e413b4126 100644 --- a/benchmark/SerializerBenchmark/Models/Notification.cs +++ b/benchmark/SerializerBenchmark/Models/Notification.cs @@ -1,8 +1,8 @@ // Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -extern alias oldmsgpack; extern alias newmsgpack; +extern alias oldmsgpack; using System; using ProtoBuf; diff --git a/benchmark/SerializerBenchmark/Models/Post.cs b/benchmark/SerializerBenchmark/Models/Post.cs index 4ec6b4ba7..c91dcd33f 100644 --- a/benchmark/SerializerBenchmark/Models/Post.cs +++ b/benchmark/SerializerBenchmark/Models/Post.cs @@ -1,8 +1,8 @@ // Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -extern alias oldmsgpack; extern alias newmsgpack; +extern alias oldmsgpack; using System; using System.Collections.Generic; diff --git a/benchmark/SerializerBenchmark/Models/Privilege.cs b/benchmark/SerializerBenchmark/Models/Privilege.cs index 77978c4c5..2260df8b2 100644 --- a/benchmark/SerializerBenchmark/Models/Privilege.cs +++ b/benchmark/SerializerBenchmark/Models/Privilege.cs @@ -1,8 +1,8 @@ // Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -extern alias oldmsgpack; extern alias newmsgpack; +extern alias oldmsgpack; using ProtoBuf; diff --git a/benchmark/SerializerBenchmark/Models/Question.cs b/benchmark/SerializerBenchmark/Models/Question.cs index 2b2198ce6..5ede917fd 100644 --- a/benchmark/SerializerBenchmark/Models/Question.cs +++ b/benchmark/SerializerBenchmark/Models/Question.cs @@ -1,8 +1,8 @@ // Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -extern alias oldmsgpack; extern alias newmsgpack; +extern alias oldmsgpack; using System; using System.Collections.Generic; diff --git a/benchmark/SerializerBenchmark/Models/QuestionTimeline.cs b/benchmark/SerializerBenchmark/Models/QuestionTimeline.cs index 5d69bd5d0..a74bd05cd 100644 --- a/benchmark/SerializerBenchmark/Models/QuestionTimeline.cs +++ b/benchmark/SerializerBenchmark/Models/QuestionTimeline.cs @@ -1,8 +1,8 @@ // Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -extern alias oldmsgpack; extern alias newmsgpack; +extern alias oldmsgpack; using System; using ProtoBuf; diff --git a/benchmark/SerializerBenchmark/Models/Reputation.cs b/benchmark/SerializerBenchmark/Models/Reputation.cs index d750dfd7b..637577aed 100644 --- a/benchmark/SerializerBenchmark/Models/Reputation.cs +++ b/benchmark/SerializerBenchmark/Models/Reputation.cs @@ -1,8 +1,8 @@ // Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -extern alias oldmsgpack; extern alias newmsgpack; +extern alias oldmsgpack; using System; using ProtoBuf; diff --git a/benchmark/SerializerBenchmark/Models/ReputationHistory.cs b/benchmark/SerializerBenchmark/Models/ReputationHistory.cs index a084eb3e2..28205064e 100644 --- a/benchmark/SerializerBenchmark/Models/ReputationHistory.cs +++ b/benchmark/SerializerBenchmark/Models/ReputationHistory.cs @@ -1,8 +1,8 @@ // Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -extern alias oldmsgpack; extern alias newmsgpack; +extern alias oldmsgpack; using System; using ProtoBuf; diff --git a/benchmark/SerializerBenchmark/Models/Revision.cs b/benchmark/SerializerBenchmark/Models/Revision.cs index b76d385c8..74da971f3 100644 --- a/benchmark/SerializerBenchmark/Models/Revision.cs +++ b/benchmark/SerializerBenchmark/Models/Revision.cs @@ -1,8 +1,8 @@ // Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -extern alias oldmsgpack; extern alias newmsgpack; +extern alias oldmsgpack; using System; using System.Collections.Generic; diff --git a/benchmark/SerializerBenchmark/Models/SearchExcerpt.cs b/benchmark/SerializerBenchmark/Models/SearchExcerpt.cs index a5d85e31a..07235c429 100644 --- a/benchmark/SerializerBenchmark/Models/SearchExcerpt.cs +++ b/benchmark/SerializerBenchmark/Models/SearchExcerpt.cs @@ -1,8 +1,8 @@ // Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -extern alias oldmsgpack; extern alias newmsgpack; +extern alias oldmsgpack; using System; using System.Collections.Generic; diff --git a/benchmark/SerializerBenchmark/Models/ShallowUser.cs b/benchmark/SerializerBenchmark/Models/ShallowUser.cs index 1c0b07055..3cda7b2ca 100644 --- a/benchmark/SerializerBenchmark/Models/ShallowUser.cs +++ b/benchmark/SerializerBenchmark/Models/ShallowUser.cs @@ -5,9 +5,8 @@ #pragma warning disable IDE1006 #pragma warning disable SA1516 -extern alias oldmsgpack; extern alias newmsgpack; - +extern alias oldmsgpack; using ProtoBuf; namespace Benchmark.Models diff --git a/benchmark/SerializerBenchmark/Models/SuggestedEdit.cs b/benchmark/SerializerBenchmark/Models/SuggestedEdit.cs index f774655ad..dcf317d61 100644 --- a/benchmark/SerializerBenchmark/Models/SuggestedEdit.cs +++ b/benchmark/SerializerBenchmark/Models/SuggestedEdit.cs @@ -1,8 +1,8 @@ // Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -extern alias oldmsgpack; extern alias newmsgpack; +extern alias oldmsgpack; using System; using System.Collections.Generic; diff --git a/benchmark/SerializerBenchmark/Models/Tag.cs b/benchmark/SerializerBenchmark/Models/Tag.cs index 586f60ffa..8ffc40d73 100644 --- a/benchmark/SerializerBenchmark/Models/Tag.cs +++ b/benchmark/SerializerBenchmark/Models/Tag.cs @@ -1,8 +1,8 @@ // Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -extern alias oldmsgpack; extern alias newmsgpack; +extern alias oldmsgpack; using System; using System.Collections.Generic; diff --git a/benchmark/SerializerBenchmark/Models/TagScore.cs b/benchmark/SerializerBenchmark/Models/TagScore.cs index b377008e2..976966731 100644 --- a/benchmark/SerializerBenchmark/Models/TagScore.cs +++ b/benchmark/SerializerBenchmark/Models/TagScore.cs @@ -1,8 +1,8 @@ // Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -extern alias oldmsgpack; extern alias newmsgpack; +extern alias oldmsgpack; using ProtoBuf; diff --git a/benchmark/SerializerBenchmark/Models/TagSynonym.cs b/benchmark/SerializerBenchmark/Models/TagSynonym.cs index 615226d1c..841e00ca4 100644 --- a/benchmark/SerializerBenchmark/Models/TagSynonym.cs +++ b/benchmark/SerializerBenchmark/Models/TagSynonym.cs @@ -1,8 +1,8 @@ // Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -extern alias oldmsgpack; extern alias newmsgpack; +extern alias oldmsgpack; using System; using ProtoBuf; diff --git a/benchmark/SerializerBenchmark/Models/TagWiki.cs b/benchmark/SerializerBenchmark/Models/TagWiki.cs index aa37ca22a..c02558ba3 100644 --- a/benchmark/SerializerBenchmark/Models/TagWiki.cs +++ b/benchmark/SerializerBenchmark/Models/TagWiki.cs @@ -1,8 +1,8 @@ // Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -extern alias oldmsgpack; extern alias newmsgpack; +extern alias oldmsgpack; using System; using ProtoBuf; diff --git a/benchmark/SerializerBenchmark/Models/TopTag.cs b/benchmark/SerializerBenchmark/Models/TopTag.cs index 2fc0c8e87..20939c30d 100644 --- a/benchmark/SerializerBenchmark/Models/TopTag.cs +++ b/benchmark/SerializerBenchmark/Models/TopTag.cs @@ -1,8 +1,8 @@ // Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -extern alias oldmsgpack; extern alias newmsgpack; +extern alias oldmsgpack; using ProtoBuf; diff --git a/benchmark/SerializerBenchmark/Models/User.cs b/benchmark/SerializerBenchmark/Models/User.cs index f78a43f43..6919b5627 100644 --- a/benchmark/SerializerBenchmark/Models/User.cs +++ b/benchmark/SerializerBenchmark/Models/User.cs @@ -4,9 +4,8 @@ #pragma warning disable IDE1006 #pragma warning disable SA1516 -extern alias oldmsgpack; extern alias newmsgpack; - +extern alias oldmsgpack; using System; using ProtoBuf; diff --git a/benchmark/SerializerBenchmark/Models/UserTimeline.cs b/benchmark/SerializerBenchmark/Models/UserTimeline.cs index 1e4410db2..f4e0119c4 100644 --- a/benchmark/SerializerBenchmark/Models/UserTimeline.cs +++ b/benchmark/SerializerBenchmark/Models/UserTimeline.cs @@ -1,8 +1,8 @@ // Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -extern alias oldmsgpack; extern alias newmsgpack; +extern alias oldmsgpack; using System; using ProtoBuf; diff --git a/benchmark/SerializerBenchmark/Models/WritePermission.cs b/benchmark/SerializerBenchmark/Models/WritePermission.cs index f450ec2aa..f82768d8f 100644 --- a/benchmark/SerializerBenchmark/Models/WritePermission.cs +++ b/benchmark/SerializerBenchmark/Models/WritePermission.cs @@ -1,8 +1,8 @@ // Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -extern alias oldmsgpack; extern alias newmsgpack; +extern alias oldmsgpack; using ProtoBuf; diff --git a/benchmark/SerializerBenchmark/SerializerBenchmark.cs b/benchmark/SerializerBenchmark/SerializerBenchmark.cs index cd58a1641..e47b42a76 100644 --- a/benchmark/SerializerBenchmark/SerializerBenchmark.cs +++ b/benchmark/SerializerBenchmark/SerializerBenchmark.cs @@ -35,7 +35,6 @@ public class AllSerializerBenchmark_BytesInOut new ProtobufNetSerializer(), new JsonNetSerializer(), new BsonNetSerializer(), - new BinaryFormatterSerializer(), new DataContractSerializer(), new HyperionSerializer(), new JilSerializer(), @@ -1116,7 +1115,6 @@ public class ShortRun_AllSerializerBenchmark_BytesInOut new ProtobufNetSerializer(), new JsonNetSerializer(), new BsonNetSerializer(), - new BinaryFormatterSerializer(), new DataContractSerializer(), new HyperionSerializer(), new JilSerializer(), diff --git a/benchmark/SerializerBenchmark/SerializerBenchmark.csproj b/benchmark/SerializerBenchmark/SerializerBenchmark.csproj index a6ea965ee..e573add58 100644 --- a/benchmark/SerializerBenchmark/SerializerBenchmark.csproj +++ b/benchmark/SerializerBenchmark/SerializerBenchmark.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 SerializerBenchmark Benchmark true diff --git a/benchmark/SerializerBenchmark/Serializers/BinaryFormatterSerializer.cs b/benchmark/SerializerBenchmark/Serializers/BinaryFormatterSerializer.cs deleted file mode 100644 index ec3891178..000000000 --- a/benchmark/SerializerBenchmark/Serializers/BinaryFormatterSerializer.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) All contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.IO; -using System.Runtime.Serialization.Formatters.Binary; - -namespace Benchmark.Serializers -{ -#pragma warning disable SYSLIB0011 // Type or member is obsolete - - public class BinaryFormatterSerializer : SerializerBase - { - public override T Deserialize(object input) - { - using (var ms = new MemoryStream((byte[])input)) - { - return (T)new BinaryFormatter().Deserialize(ms); - } - } - - public override object Serialize(T input) - { - using (var ms = new MemoryStream()) - { - new BinaryFormatter().Serialize(ms, input); - ms.Flush(); - return ms.ToArray(); - } - } - - public override string ToString() - { - return "BinaryFormatter"; - } - } -} diff --git a/benchmark/SerializerBenchmark/Serializers/MessagePackSerializer.cs b/benchmark/SerializerBenchmark/Serializers/MessagePackSerializer.cs index f9438c98c..95618ba97 100644 --- a/benchmark/SerializerBenchmark/Serializers/MessagePackSerializer.cs +++ b/benchmark/SerializerBenchmark/Serializers/MessagePackSerializer.cs @@ -1,8 +1,8 @@ // Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -extern alias oldmsgpack; extern alias newmsgpack; +extern alias oldmsgpack; #pragma warning disable SA1402 // File may only contain a single type #pragma warning disable SA1649 // File name should match first type name @@ -206,9 +206,9 @@ private OptimizedResolver() private static class Cache { - #pragma warning disable SA1401 // Fields should be private +#pragma warning disable SA1401 // Fields should be private public static newmsgpack::MessagePack.Formatters.IMessagePackFormatter Formatter; - #pragma warning restore SA1401 // Fields should be private +#pragma warning restore SA1401 // Fields should be private static Cache() { diff --git a/bin/MessagePack/Debug/netstandard2.1/MessagePack.Analyzers.CodeFixes.dll.meta b/bin/MessagePack/Debug/netstandard2.1/MessagePack.Analyzers.CodeFixes.dll.meta new file mode 100644 index 000000000..0b2f22a5e --- /dev/null +++ b/bin/MessagePack/Debug/netstandard2.1/MessagePack.Analyzers.CodeFixes.dll.meta @@ -0,0 +1,71 @@ +fileFormatVersion: 2 +guid: 891c05d4b21294248acbe7e80ae1a4fb +labels: +- RoslynAnalyzer +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 1 + isExplicitlyReferenced: 1 + validateReferences: 0 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude Win: 1 + Exclude Win64: 1 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: None + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/bin/MessagePack/Debug/netstandard2.1/MessagePack.Analyzers.CodeFixes.xml.meta b/bin/MessagePack/Debug/netstandard2.1/MessagePack.Analyzers.CodeFixes.xml.meta new file mode 100644 index 000000000..7ab33553f --- /dev/null +++ b/bin/MessagePack/Debug/netstandard2.1/MessagePack.Analyzers.CodeFixes.xml.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4f2de8ccc64b20e4c9a0396761e4833a +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/bin/MessagePack/Debug/netstandard2.1/MessagePack.Analyzers.dll.meta b/bin/MessagePack/Debug/netstandard2.1/MessagePack.Analyzers.dll.meta new file mode 100644 index 000000000..3f2b25b6c --- /dev/null +++ b/bin/MessagePack/Debug/netstandard2.1/MessagePack.Analyzers.dll.meta @@ -0,0 +1,45 @@ +fileFormatVersion: 2 +guid: 892214273c923364eb684f3d7859fe0b +labels: +- RoslynAnalyzer +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 1 + isExplicitlyReferenced: 1 + validateReferences: 0 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude Win: 1 + Exclude Win64: 1 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/bin/MessagePack/Debug/netstandard2.1/MessagePack.Analyzers.xml.meta b/bin/MessagePack/Debug/netstandard2.1/MessagePack.Analyzers.xml.meta new file mode 100644 index 000000000..40a2450ee --- /dev/null +++ b/bin/MessagePack/Debug/netstandard2.1/MessagePack.Analyzers.xml.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 88463aa60e4b9b441adcbe51a85df913 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/bin/MessagePack/Debug/netstandard2.1/MessagePack.Annotations.dll.meta b/bin/MessagePack/Debug/netstandard2.1/MessagePack.Annotations.dll.meta new file mode 100644 index 000000000..a0cde4960 --- /dev/null +++ b/bin/MessagePack/Debug/netstandard2.1/MessagePack.Annotations.dll.meta @@ -0,0 +1,33 @@ +fileFormatVersion: 2 +guid: 3f561a38e71988440b2d52ebba1a0521 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 1 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/bin/MessagePack/Debug/netstandard2.1/MessagePack.Annotations.xml.meta b/bin/MessagePack/Debug/netstandard2.1/MessagePack.Annotations.xml.meta new file mode 100644 index 000000000..3807f7c74 --- /dev/null +++ b/bin/MessagePack/Debug/netstandard2.1/MessagePack.Annotations.xml.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 540772e8d33295746891820d923d6eff +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/bin/MessagePack/Debug/netstandard2.1/MessagePack.SourceGenerator.dll.meta b/bin/MessagePack/Debug/netstandard2.1/MessagePack.SourceGenerator.dll.meta new file mode 100644 index 000000000..c0b1f55ec --- /dev/null +++ b/bin/MessagePack/Debug/netstandard2.1/MessagePack.SourceGenerator.dll.meta @@ -0,0 +1,45 @@ +fileFormatVersion: 2 +guid: 9842f348649c87c4f8dd184cac2508aa +labels: +- RoslynAnalyzer +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 1 + isExplicitlyReferenced: 1 + validateReferences: 0 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude Win: 1 + Exclude Win64: 1 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/bin/MessagePack/Debug/netstandard2.1/MessagePack.SourceGenerator.xml.meta b/bin/MessagePack/Debug/netstandard2.1/MessagePack.SourceGenerator.xml.meta new file mode 100644 index 000000000..5e478298d --- /dev/null +++ b/bin/MessagePack/Debug/netstandard2.1/MessagePack.SourceGenerator.xml.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 5b419071c2e21e9469fa6971585b48f3 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/bin/MessagePack/Debug/netstandard2.1/MessagePack.deps.json.meta b/bin/MessagePack/Debug/netstandard2.1/MessagePack.deps.json.meta new file mode 100644 index 000000000..c56c26511 --- /dev/null +++ b/bin/MessagePack/Debug/netstandard2.1/MessagePack.deps.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 2aa609d1a0a772b4791a8bfabc13df17 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/bin/MessagePack/Debug/netstandard2.1/MessagePack.dll.meta b/bin/MessagePack/Debug/netstandard2.1/MessagePack.dll.meta new file mode 100644 index 000000000..348efd133 --- /dev/null +++ b/bin/MessagePack/Debug/netstandard2.1/MessagePack.dll.meta @@ -0,0 +1,33 @@ +fileFormatVersion: 2 +guid: 28339a892bc291940a4a15f087c29fde +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 1 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/bin/MessagePack/Debug/netstandard2.1/MessagePack.xml.meta b/bin/MessagePack/Debug/netstandard2.1/MessagePack.xml.meta new file mode 100644 index 000000000..f717945b2 --- /dev/null +++ b/bin/MessagePack/Debug/netstandard2.1/MessagePack.xml.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 7fb1b483dd594c143bfd6495d9e9b234 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/bin/MessagePack/Debug/netstandard2.1/package.json b/bin/MessagePack/Debug/netstandard2.1/package.json new file mode 100644 index 000000000..98e6a4380 --- /dev/null +++ b/bin/MessagePack/Debug/netstandard2.1/package.json @@ -0,0 +1,12 @@ +{ + "name": "com.github.messagepack.internal", + "displayName": "MessagePack Internal", + "author": { "name": "MessagePack-CSharp", "url": "https://github.com/MessagePack-CSharp/MessagePack-CSharp" }, + "version": "1.0.0", + "unity": "2021.3", + "description": "Internal Package of MessagePack for development time.", + "keywords": [ "Serialization" ], + "license": "MIT", + "category": "Scripting", + "dependencies": {} +} diff --git a/bin/MessagePack/Debug/netstandard2.1/package.json.meta b/bin/MessagePack/Debug/netstandard2.1/package.json.meta new file mode 100644 index 000000000..8878a36ef --- /dev/null +++ b/bin/MessagePack/Debug/netstandard2.1/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: b958014f8e837e34aa1a78db9cd582de +PackageManifestImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/doc/analyzers/MsgPack003.md b/doc/analyzers/MsgPack003.md index 799e2baf5..65fa74c1f 100644 --- a/doc/analyzers/MsgPack003.md +++ b/doc/analyzers/MsgPack003.md @@ -1,3 +1,47 @@ # MsgPack003 Use MessagePackObjectAttribute -Type must be marked with `MessagePackObjectAttribute`. +Types referenced by serializable fields and properties must themselves be attributed with the `[MessagePackObject]`. + +## Examples of patterns that are flagged by this analyzer + +```cs +[MessagePackObject] +public class A { + [Key(0)] + public B b; +} + +public class B { + public int Count; +} +``` + +## Example fix + +Add the required attributes to the `B` class so that a dynamic formatter can be generated for it: + +```cs +[MessagePackObject] +public class A { + [Key(0)] + public B b; +} + +[MessagePackObject] +public class B { + [Key(0)] + public int Count; +} +``` + +An automated code fix is offered for this. + +## Alternative fix + +When `B` is formattable via a custom formatter, the diagnostic may be suppressed by declaring this attribute: + +```cs +[assembly: MessagePackAssumedFormattable(typeof(B))] +``` + +When doing so, it becomes your responsibility to ensure that the custom formatter for `B` is discoverable via the `IFormatterResolver` object accessible through `MessagePackSerializerOptions.Resolver`. diff --git a/doc/analyzers/MsgPack004.md b/doc/analyzers/MsgPack004.md index 5ced79ef2..94bcdc10c 100644 --- a/doc/analyzers/MsgPack004.md +++ b/doc/analyzers/MsgPack004.md @@ -1,3 +1,47 @@ # MsgPack004 Attribute public members of MessagePack objects Public members of `MessagePackObjectAttribute`-attributed types require either `KeyAttribute` or `IgnoreMemberAttribute`. + +## Examples of patterns that are flagged by this analyzer + +```cs +[MessagePackObject] +public class C { + [Key(0)] + public int A { get; set; } + + public int B { get; set; } // MsgPack004 +} +``` + +## Typical fix + +Indicate that the unattributed member should be serialized: + +```cs +[MessagePackObject] +public class C { + [Key(0)] + public int A { get; set; } + + [Key(1)] + public int B { get; set; } +} +``` + +An automated code fix is available for this. + +## Alternative fix + +If the unattributed member should _not_ be serialized, apply the `[IgnoreMember]` attribute: + +```cs +[MessagePackObject] +public class C { + [Key(0)] + public int A { get; set; } + + [IgnoreMember] + public int B { get; set; } +} +``` diff --git a/doc/analyzers/MsgPack005.md b/doc/analyzers/MsgPack005.md index 9d209e2f9..1a8783fa0 100644 --- a/doc/analyzers/MsgPack005.md +++ b/doc/analyzers/MsgPack005.md @@ -1,3 +1,6 @@ # MsgPack005 MessagePackObject validation Invalid MessagePackObject definition. + +There are a variety of conditions that can produce this diagnostic. +Examine the specific message to understand what is wrong and how to correct it. diff --git a/doc/analyzers/MsgPack006.md b/doc/analyzers/MsgPack006.md new file mode 100644 index 000000000..397b6eec6 --- /dev/null +++ b/doc/analyzers/MsgPack006.md @@ -0,0 +1,36 @@ +# MsgPack006 Type must be of `IMessagePackFormatter` + +This diganostic appears when the type passed to `MesssagePackFormatterAttribute` does not actually implement some `IMessagePackFormatter`. + +## Examples of patterns that are flagged by this analyzer + +```cs +[MessagePackObject] +public class A { + [Key(0), MessagePackFormatter(typeof(CustomBFormatter))] + public B b; +} + +public class CustomBFormatter {} +``` + +## Typical fix + +Change the attribute to point to a valid formatter, or update the referenced class to be a valid formatter. +The following example takes the latter approach. + +```cs +[MessagePackObject] +public class A { + [Key(0), MessagePackFormatter(typeof(CustomBFormatter))] + public B b; +} + +public class CustomBFormatter : IMessagePackFormatter { + void Serialize(ref MessagePackWriter writer, B value, MessagePackSerializerOptions options) + => throw new NotImplementedException(); + + B Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) + => throw new NotImplementedException(); +} +``` diff --git a/doc/analyzers/MsgPack007.md b/doc/analyzers/MsgPack007.md new file mode 100644 index 000000000..096162795 --- /dev/null +++ b/doc/analyzers/MsgPack007.md @@ -0,0 +1,4 @@ +# MsgPack007 Deserializing constructors + +There are a variety of conditions that can produce this diagnostic. +Examine the specific message to understand what is wrong and how to correct it. diff --git a/doc/analyzers/MsgPack008.md b/doc/analyzers/MsgPack008.md new file mode 100644 index 000000000..6ee013a3a --- /dev/null +++ b/doc/analyzers/MsgPack008.md @@ -0,0 +1,6 @@ +# MsgPack008 AOT limitations + +AOT source generated formatters do not support certain features that formatters generated dynamically at runtime may support. + +There are a variety of conditions that can produce this diagnostic. +Examine the specific message to understand what is wrong and how to correct it. diff --git a/doc/analyzers/MsgPack009.md b/doc/analyzers/MsgPack009.md new file mode 100644 index 000000000..db6cebd89 --- /dev/null +++ b/doc/analyzers/MsgPack009.md @@ -0,0 +1,9 @@ +# MsgPack009 Colliding Formatters + +All formatters in a compilation are automatically added to a source generated resolver so that it can be found at runtime. + +When two formatters implement `IMessagePackFormatter` for the same `T`, it cannot be statically determined which formatter should be used, and this diagnostic results. + +## Typical fix + +Either remove all but one of the conflicting formatters, or exclude all but one from inclusion in the source generated resolver by applying the `[ExcludeFormatterFromSourceGeneratedResolver]` attribute to some of the formatters. diff --git a/doc/analyzers/MsgPack010.md b/doc/analyzers/MsgPack010.md new file mode 100644 index 000000000..5512b3f2c --- /dev/null +++ b/doc/analyzers/MsgPack010.md @@ -0,0 +1,35 @@ +# MsgPack010 Inaccessible Formatter + +Formatters must be declared with `internal` or `public` visibility so that the source generated resolver can access them. + +This tends to happen when a formatter is declared as a nested type, where C# defaults to `private` visibility. + +## Examples of patterns that are flagged by this analyzer + +```cs +class Outer { + /*private*/ class CustomBFormatter : IMessagePackFormatter { // MsgPack010 + void Serialize(ref MessagePackWriter writer, B value, MessagePackSerializerOptions options) + => throw new NotImplementedException(); + + B Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) + => throw new NotImplementedException(); + } +} +``` + +## Typical fix + +Add the `internal` or `public` modifier to the nested formatter: + +```cs +class Outer { + internal class CustomBFormatter : IMessagePackFormatter { + void Serialize(ref MessagePackWriter writer, B value, MessagePackSerializerOptions options) + => throw new NotImplementedException(); + + B Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) + => throw new NotImplementedException(); + } +} +``` diff --git a/doc/analyzers/MsgPack011.md b/doc/analyzers/MsgPack011.md new file mode 100644 index 000000000..2b6e8750e --- /dev/null +++ b/doc/analyzers/MsgPack011.md @@ -0,0 +1,39 @@ +# MsgPack011 Partial type required + +When a `[MessagePackObject]` includes serializable members with less than `internal` visibility, the declaring type must use the `partial` modifier, so that the source generated formatter can be emitted as a member of that type and thus gain access to its private members. + +## Examples of patterns that are flagged by this analyzer + +```cs +[MessagePackObject] +public class A { + [Key(0)] + private B b; +} +``` + +## Typical fix + +Add the `partial` keyword: + +```cs +[MessagePackObject] +public partial class A { + [Key(0)] + private B b; +} +``` + +An automated code fix is available for this. + +## Alternative fix + +Alternatively, make all private/protected serializable members `internal` instead so that a formatter declared elsewhere in the compilation can reach them: + +```cs +[MessagePackObject] +public class A { + [Key(0)] + internal B b; +} +``` diff --git a/doc/analyzers/MsgPack012.md b/doc/analyzers/MsgPack012.md new file mode 100644 index 000000000..3ffb8223c --- /dev/null +++ b/doc/analyzers/MsgPack012.md @@ -0,0 +1,24 @@ +# MsgPack012 Inaccessible data type + +Serializable data types must have at least `internal` accessibility so that a source generated formatter elsewhere in the compilation can access it. +C# defaults to `internal` visibility except for nested types, which receive `private` visibility by default. + +## Examples of patterns that are flagged by this analyzer + +```cs +class Outer { + [MessagePackObject] + /*private*/ class DataType { } +} +``` + +## Typical fix + +Add the `internal` or `public` modifier to the nested data type: + +```cs +class Outer { + [MessagePackObject] + internal class DataType { } +} +``` diff --git a/doc/analyzers/MsgPack013.md b/doc/analyzers/MsgPack013.md new file mode 100644 index 000000000..85dd67572 --- /dev/null +++ b/doc/analyzers/MsgPack013.md @@ -0,0 +1,52 @@ +# MsgPack013 Inaccessible formatter instance + +A custom `IMessagePackFormatter` implementation must have an accessible instance to be included in the source generated resolver. + +This instance can be exposed by any of the following: + +1. A public default constructor. +1. A public static readonly field called `Instance`. + +## Examples of patterns that are flagged by this analyzer + +```cs +class CustomBFormatter : IMessagePackFormatter { // MsgPack013 + private CustomBFormatter { } // hides the default public constructor + + void Serialize(ref MessagePackWriter writer, B value, MessagePackSerializerOptions options) + => throw new NotImplementedException(); + + B Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) + => throw new NotImplementedException(); +} +``` + +## Typical fix + +Delete the non-public default constructor so that C# can access it: + +```cs +class CustomBFormatter : IMessagePackFormatter { + void Serialize(ref MessagePackWriter writer, B value, MessagePackSerializerOptions options) + => throw new NotImplementedException(); + + B Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) + => throw new NotImplementedException(); +} +``` + +Or make a singleton instance available: + +```cs +class CustomBFormatter : IMessagePackFormatter { + public static readonly CustomBFormatter Instance = new(); + + private CustomBFormatter { } // hides the default public constructor + + void Serialize(ref MessagePackWriter writer, B value, MessagePackSerializerOptions options) + => throw new NotImplementedException(); + + B Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) + => throw new NotImplementedException(); +} +``` diff --git a/doc/analyzers/MsgPack014.md b/doc/analyzers/MsgPack014.md new file mode 100644 index 000000000..b0cbf1b02 --- /dev/null +++ b/doc/analyzers/MsgPack014.md @@ -0,0 +1,51 @@ +# MsgPack014 Nullable reference type formatter + +Custom formatters of reference types should be prepared to handle `null` references. + +## Examples of patterns that are flagged by this analyzer + +```cs +class B { } + +class CustomBFormatter : IMessagePackFormatter { // MsgPack014 + void Serialize(ref MessagePackWriter writer, B value, MessagePackSerializerOptions options) { + writer.WriteArrayHeader(0); + } + + B Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) { + int count = reader.ReadArrayHeader(); + for (int i = 0; i < count; i++) + reader.Skip(); + } +} +``` + +## Typical fix + + +```cs +class B { } + +class CustomBFormatter : IMessagePackFormatter { + void Serialize(ref MessagePackWriter writer, B? value, MessagePackSerializerOptions options) { + if (value is null) { + writer.WriteNil(); + return; + } + + writer.WriteArrayHeader(0); + } + + B? Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) { + if (reader.TryReadNil()) return null; + + int count = reader.ReadArrayHeader(); + for (int i = 0; i < count; i++) + reader.Skip(); + } +} +``` + +An automated code fix will add the nullable ref annotations. +But it does not automatically add the null handling to the method bodies. +This must be done by hand. diff --git a/doc/analyzers/MsgPack015.md b/doc/analyzers/MsgPack015.md new file mode 100644 index 000000000..87e8d9dd1 --- /dev/null +++ b/doc/analyzers/MsgPack015.md @@ -0,0 +1,42 @@ +# MsgPack015 MessagePackObjectAttribute.AllowPrivate should be set + +When a `[MessagePackObject]`-attributed type cannot be serialized without access to non-public members, the `AllowPrivate` property should be set on the `MessagePackObjectAttribute` so that: + +- Dynamically generated formatters can serialize the type, even if `DynamicObjectResolver` is used instead of `DynamicObjectResolverAllowPrivate`. +- Analyzers can help ensure proper annotation of non-public members. + +## Examples of patterns that are flagged by this analyzer + +### Internal members + +```cs +[MessagePackObject] +public class MyData { + [Key(0)] + internal int Foo { get; set; } +} +``` + +### Internal type + +```cs +[MessagePackObject] +internal class MyData { + [Key(0)] + public int Foo { get; set; } +} +``` + +## Typical fix + +Simply add the `AllowPrivate = true` syntax to the `[MessagePackObject]` attribute: + +```cs +[MessagePackObject(AllowPrivate = true)] +internal class MyData { + [Key(0)] + internal int Foo { get; set; } +} +``` + +An automated code fix is offered for this diagnostic. diff --git a/doc/analyzers/MsgPack016.md b/doc/analyzers/MsgPack016.md new file mode 100644 index 000000000..b38584880 --- /dev/null +++ b/doc/analyzers/MsgPack016.md @@ -0,0 +1,54 @@ +# MsgPack016 KeyAttribute-derived attributes are not supported by AOT formatters + +When a `[MessagePackObject]`-attributed type attributes its fields and properties with attributes that derive from `KeyAttribute` rather than from `KeyAttribute` itself, that type is incompatible with AOT source generated formatters. + +## Examples of patterns that are flagged by this analyzer + +```cs +[MessagePackObject] +public class A +{ + [CompositeKey(0, 1)] + public string Prop1 { get; set; } + + [CompositeKey(0, 2)] + public string Prop2 { get; set; } +} + +public class CompositeKeyAttribute : KeyAttribute +{ + public CompositeKeyAttribute(byte level, int index) + : base(CreateKey(level, index)) { } + + private static string CreateKey(byte level, int index) + { + var c = (char)('A' + level); + return c + index.ToString("x"); + } +} +``` + +## Typical fix + +If you intend to keep using the derived attributes type, suppress the warning by turning off source generated formatter generation for that type: + +```diff +-[MessagePackObject] ++[MessagePackObject(SuppressSourceGeneration = true)] + public class A +``` + +Alternatively, enable propery AOT formatter source generation by switching to the standard `KeyAttribute`: + +```cs +[MessagePackObject] +public class A +{ + [Key("A1")] + public string Prop1 { get; set; } + + [Key("A2")] + public string Prop2 { get; set; } +} +``` + diff --git a/doc/analyzers/MsgPack017.md b/doc/analyzers/MsgPack017.md new file mode 100644 index 000000000..32bdd84c9 --- /dev/null +++ b/doc/analyzers/MsgPack017.md @@ -0,0 +1,87 @@ +# MsgPack017 Property with init accessor and initializer + +When a `[MessagePackObject]`-attributed type declares a serialized property with an `init` setter (as opposed to a typical `set` setter) and +defines an initializer on that property, the default assigned by that initializer will be replaced with the default value for the type upon deserialization. + +## Examples of patterns that are flagged by this analyzer + +```cs +[MessagePackObject] +public class A +{ + [Key("Prop1")] + public string Prop1 { get; init; } = "This is the default."; // Diagnostic emitted here + + [Key("Prop2")] + public string Prop2 { get; set; } = "Another default." + + [Key("Prop3")] + public string Prop3 { get; set; } +} +``` + +Deserializing this class may have unexpected behavior when a value is not given for `Prop1` during deserialization. +Consider the case of deserializing the msgpack equivalent of the following data payload: + +```json +{ "Prop3": "hello" } +``` + +One might expect that when `A` is deserialized, it will have the following data: + +Property | Value +--|-- +Prop1 | "This is the default." +Prop2 | "Another default. +Prop3 | "hello" + +But in fact it will have the following values: + +Property | Value +--|-- +Prop1 | null +Prop2 | "Another default. +Prop3 | "hello" + +This is because as an `init` property, the only way the AOT source generated formatter can set the property's value is in an object initializer, like this: + +```cs +// Values here are replaced if a value is given in deserialized data. +string deserializedProp1Value = default(string); +string deserializedProp2Value = default(string); + +// Keep track of which values are actually specified. +bool deserializedProp2Value_specified = false; + +// process msgpack data here, possibly replacing deserializedProp1Value with an actual value. + +A deserializedResult = new A +{ + Prop1 = deserializedProp1Value; +} + +if (deserializedProp2Value_specified) + deserializedResult.Prop2 = deserializedProp2Value; + +return deserializedResult; +``` + +Notice how a property with a `set` setter may be conditionally set, and therefore it is only set when a value was specified by the data. +But the property with an `init` setter may only be set in the object initializer, for which C# provides no means to set conditionally. + +## Typical fix + +If it is important to specify a default value that is different from the default value for the type (e.g. `null` for reference types or `0` for integers), you should declare a `set` accessor instead of an `init` accessor. + +```diff +- public string Prop1 { get; init; } = "This is the default."; ++ public string Prop1 { get; set; } = "This is the default."; +``` + +Alternatively, disable source generated formatters for this type, forcing a dynamic one to be created at runtime which *does* have the ability to conditionally set the property. + +```diff +-[MessagePackObject] ++[MessagePackObject(SuppressSourceGeneration = true)] + public class A +``` diff --git a/doc/analyzers/MsgPack018.md b/doc/analyzers/MsgPack018.md new file mode 100644 index 000000000..be7dc72f2 --- /dev/null +++ b/doc/analyzers/MsgPack018.md @@ -0,0 +1,56 @@ +# MsgPack018 Unique names required in force map mode + +`[MessagePackObject]`-attributed types may omit attributing each member with a `[Key]` attribute using forced map mode. +In that mode, all serialized members *must* have unique names or a key collision would result in the serialized object. + +## Examples of patterns that are flagged by this analyzer + +```cs +[MessagePackObject] +public class A +{ + public string Prop1 { get; set; } +} + +[MessagePackObject] +public class B : A +{ + public new string Prop1 { get; set; } // Diagnostic reported here due to redefinition of Prop1 +} +``` + +## Typical fix + +Rename one of the colliding properties: + +```cs +[MessagePackObject] +public class A +{ + public string Prop1 { get; set; } +} + +[MessagePackObject] +public class B : A +{ + public string Prop2 { get; set; } +} +``` + +Or add a `[Key]` attribute that assigns a unique serialized key to that member: + + +```cs +[MessagePackObject] +public class A +{ + public string Prop1 { get; set; } +} + +[MessagePackObject] +public class B : A +{ + [Key("B_Prop1")] + public new string Prop1 { get; set; } +} +``` diff --git a/doc/analyzers/index.md b/doc/analyzers/index.md index 0d2b17edb..38365b681 100644 --- a/doc/analyzers/index.md +++ b/doc/analyzers/index.md @@ -10,5 +10,18 @@ ID | Title [MsgPack003](MsgPack003.md) | Use MessagePackObjectAttribute [MsgPack004](MsgPack004.md) | Attribute public members of MessagePack objects [MsgPack005](MsgPack005.md) | MessagePackObject validation +[MsgPack006](MsgPack006.md) | Type must be of `IMessagePackFormatter` +[MsgPack007](MsgPack007.md) | Deserializing constructors +[MsgPack008](MsgPack008.md) | AOT limitations +[MsgPack009](MsgPack009.md) | Colliding Formatters +[MsgPack010](MsgPack010.md) | Inaccessible Formatter +[MsgPack011](MsgPack011.md) | Partial type required +[MsgPack012](MsgPack012.md) | Inaccessible data type +[MsgPack013](MsgPack013.md) | Inaccessible formatter instance +[MsgPack014](MsgPack014.md) | Nullable reference type formatter +[MsgPack015](MsgPack015.md) | MessagePackObjectAttribute.AllowPrivate should be set +[MsgPack016](MsgPack016.md) | KeyAttribute-derived attributes are not supported by AOT formatters +[MsgPack017](MsgPack017.md) | Property with init accessor and initializer +[MsgPack018](MsgPack018.md) | Unique names required in force map mode [1]: https://nuget.org/packages/MessagePackAnalyzer diff --git a/doc/mark_of_the_web.png b/doc/mark_of_the_web.png new file mode 100644 index 000000000..634ea6123 Binary files /dev/null and b/doc/mark_of_the_web.png differ diff --git a/doc/migrating_v1-v2.md b/doc/migrating_v1-v2.md new file mode 100644 index 000000000..30bd34e47 --- /dev/null +++ b/doc/migrating_v1-v2.md @@ -0,0 +1,393 @@ +# Migrating from MessagePack v1.x to MessagePack v2.x + +MessagePack 2.0 contains many breaking changes since the 1.x versions. +These include both binary and source breaking changes, meaning you may need to update your source code as well as recompile against the 2.x version. + +The v1.x version will still be serviced for security fixes, but new features will tend to only be offered in the 2.x versions. + +Update your package references from the 1.x version you use to the 2.x version. If your project compiles, you may be done. +Otherwise work through each compiler error. Some common ones you may face are listed below with suggested fixes. + +If you own an application that has a mix of MessagePack consumers and not all of them can be upgraded to v2.x at once, you can offer both MessagePack v1.x and v2.x assemblies with your application so that each user can find the one it needs. [Here is a sample](https://github.com/AArnott/MessagePackDualVersions). + +## API changes + +### MessagePackSerializerOptions + +A new `MessagePackSerializerOptions` class becomes a first class citizen in this library. +It encapsulates the `IFormatterResolver` that used to be passed around by itself. +It also includes several other settings that may influence how `MessagePackSerializerOptions` or some of the +formatters may operate. + +Because this new class tends to get saved to public static properties, it is immutable to ensure it can be shared safely. +Each property `Foo` on the class includes a `WithFoo` method which clones the instance and returns the new instance with just that one property changed. + +To support this new options class and avoid unnecessary allocations from the copy-and-mutate methods, many of the popular resolvers now expose a public static `Options` property with the resolver preset to itself. So for example, you may use: + +```cs +var msgpack = MessagePackSerializer.Serialize(objectGraph, StandardResolverAllowPrivate.Options); +var deserializedGraph = MessagePackSerializer.Deserialize(msgpack, StandardResolverAllowPrivate.Options); +``` + +If you want to combine a particular resolver with other options changes (e.g. enabling LZ4 compression), you may do that too: + +```cs +var options = StandardResolverAllowPrivate.Options.WithCompression(MessagePackCompression.Lz4BlockArray); +var msgpack = MessagePackSerializer.Serialize(objectGraph, options); +var deserializedGraph = MessagePackSerializer.Deserialize(msgpack, options); +``` + +An equivalent options instance can be created manually: + +```cs +var options = MessagePackSerializerOptions.Standard + .WithCompression(MessagePackCompression.Lz4BlockArray) + .WithResolver(StandardResolverAllowPrivate.Instance); +``` + +### MessagePackSerializer class + +#### Serialization + +Serializing object graphs to msgpack is now based on `IBufferWriter` instead of `ref byte[]`. +This allows for serializing very large object graphs without repeatedly allocating ever-larger arrays and copying the previously serialized msgpack bytes from the smaller buffer to the larger one. +`IBufferWriter` can direct the written msgpack bytes directly to a pipe, a file, or anywhere else you choose, allowing you to avoid a buffer copy within your own code as well. + +An `IBufferWriter` is always wrapped by the new `MessagePackWriter` struct. + +Many overloads of the `Serialize` method exist which ultimately all call the overload that accepts a `MessagePackWriter`. + +#### Deserialization + +Deserializing msgpack sequences is now much more flexible. +Instead of deserializing from `byte[]` or `ArraySegment` only, you can deserialize from any `ReadOnlyMemory` or `ReadOnlySequence` instance. + +`ReadOnlyMemory` is like `ArraySegment` but more friendly and can refer to contiguous memory anywhere including native pointers. You can pass a `byte[]` or `ArraySegment` in anywhere that `ReadOnlyMemory` is expected and C# will implicitly cast for you (without any buffer copying). + +`ReadOnlySequence` allows for deserialization from non-continguously allocated memory, enabling you to deserialize very large msgpack sequences without risking an `OutOfMemoryException` due simply to the inability to find large amounts of free contiguous memory. + +Many overloads of the `Deserialize` method exists which ultimately all call the overload that accepts a `MessagePackReader`. + +#### Deserializing from a Stream + +Deserializing from a `Stream` has changed from v1.x to v2.0. The `readStrict` parameter has been removed and in v2.x +the `MessagePackSerializer.Deserialize{Async}(Stream)` methods act as if `readStrict: false` in v1.x. +This works great and is the preferred API to use when the entire `Stream` is expected to contain exactly one +top-level messagepack structure that you want to deserialize. + +For performance reasons, the entire `Stream` is read into memory before deserialization begins. +If there is more data on the `Stream` than the messagepack structure to be deserialized, +the deserialization will ignore the excess data, but the excess data wouldn't be on the `Stream` +any more to be read later. + +If the `Stream` is seekable (that is, its `CanSeek` property returns `true`) then after deserialization +is complete the `Stream` will be repositioned to the first byte after the messagepack data structure +that was deserialized. This means you'll get the `Stream` back as you might expect it, but only after +you paid a perf cost of "reading" more data than was necessary to deserialize. + +If the `Stream` is *not* seekable (e.g. a network stream) or contains multiple top-level messagepack +data structures consecutively, MessagePack 2.0 adds a new, more performant way to read each +messagepack structure. It's analogous to v1.x's `readStrict: true` mode, but is much more performant. +It comes in the form of the new `MessagePackStreamReader` class, and can be easily used as follows: + +```cs +static async Task> DeserializeListFromStreamAsync(Stream stream, CancellationToken cancellationToken) +{ + var dataStructures = new List(); + using (var streamReader = new MessagePackStreamReader(stream)) + { + while (await streamReader.ReadAsync(cancellationToken) is ReadOnlySequence msgpack) + { + dataStructures.Add(MessagePackSerializer.Deserialize(msgpack, cancellationToken: cancellationToken)); + } + } + + return dataStructures; +} +``` + +#### Default behavior + +The `DefaultResolver` static property has been replaced with the `DefaultOptions` static property. +Just as with v1.x, in v2.x this static property influences how serialization occurs +when the value is not explicitly specified when invoking one of the `MessagePackSerializer` methods. + +**WARNING**: When developing a simple application where you control all MessagePack-related code it may be safe to rely on this mutable static to control behavior. +For all other libraries or multi-purpose applications that use `MessagePackSerializer` you should explicitly specify the `MessagePackSerializerOptions` to use with each method invocation to guarantee your code behaves as you expect even when sharing an `AppDomain` or process with other MessagePack users that may change this static property. + +#### Non-generic methods + +In v1.x non-generic methods for serialization/deserialization were exposed on the nested `MessagePackSerializer.NonGeneric` class. +In v2.x these overloads are moved to the `MessagePackSerializer` class itself. + +The `MessagePackSerializer.Typeless` nested class in v1.x remains in v2.x, but with a modified set of overloads. + +#### JSON converting methods + +In v1.x the `MessagePackSerializer` class exposed methods both to serialize an object graph to JSON, +as well as converting between msgpack and JSON. These two translations were very different but were mere overloads of each other. +In v2.x these methods have been renamed for clarity. +The methods `ConvertFromJson` and `ConvertToJson` translates between JSON and msgpack binary. +The method `SerializeToJson` translates an object graph to JSON. + +#### LZ4MessagePackSerializer + +The `LZ4MessagePackSerializer` class has been removed. +Instead, use `MessagePackSerializer` and pass in a `MessagePackSerializerOptions` with `WithCompression` set to `MessagePackCompression.Lz4Block` or `MessagePackCompression.Lz4BlockArray`. + +For example, make this change: + +```diff +-byte[] buffer = LZ4MessagePackSerializer.Serialize("hi"); ++static readonly lz4Options = MessagePackSerializerOptions.Standard.WithCompression(MessagePackCompression.Lz4BlockArray); ++byte[] buffer = MessagePackSerializer.Serialize("hi", lz4Options); +``` + +`Lz4Block` is same as v1 LZ4MessagePackSerializer. `Lz4BlockArray` is new compression mode of v2. Regardless of which Lz4 option is set at the deserialization, both data can be deserialized. For example, when the option is `Lz4BlockArray`, binary data of both `Lz4Block` and `Lz4BlockArray` can be deserialized. + +### Thrown exceptions + +In v1.x any exception thrown during serialization or deserialization was uncaught and propagated to the application. +In v2.x all exceptions are caught by the `MessagePackSerializer` and rethrown as an inner exception of `MessagePackSerializationException`. +This makes it easier to write code to catch exceptions during serialization since you can now catch just one specific type of exception. + +### Built-in resolvers + +The following resolvers have been *removed*: + +| Removed v1.x formatter | v2.x alternative | +|--|--| +| `UnsafeBinaryResolver` | `NativeDecimalResolver`, `NativeGuidResolver` + +#### CompositeResolver + +In v1.x the `CompositeResolver` type could only be used once and mutated a static property. +In v2.x the `CompositeResolver` type no longer mutates any statics and thus can be used safely by many callers that simply want to aggregate many formatters and/or resolvers into one resolver. This often removes the need for you to define your own `IFormatterResolver`. + +For example if you have written a custom formatter and want to use that in addition to what the `StandardResolver` offers, you can easily compose an aggregate resolver like this: + +```cs +var resolver = CompositeResolver.Create( + new IMessagePackFormatter[] { MyCustomFormatter.Instance }, + new IFormatterResolver[] { StandardResolver.Instance } +); +var options = MessagePackSerializerOptions.Standard.WithResolver(resolver); +var msgpack = MessagePackSerializer.Serialize(objectGraph, options); +var deserializedGraph = MessagePackSerializer.Deserialize(msgpack, options); +``` + +### Built-in formatters + +The following formatters have been *removed*: + +| Removed v1.x formatter | v2.x alternative | +|--|--| +| `BinaryDecimalFormatter` | `NativeDecimalFormatter` +| `BinaryGuidFormatter` | `NativeGuidFormatter` +| `FourDimentionalArrayFormatter` | `FourDimensionalArrayFormatter` +| `OldSpecBinaryFormatter` | Use `MessagePackSerializerOptions.OldSpec` or `MessagePackWriter.OldSpec` instead. +| `OldSpecStringFormatter` | Use `MessagePackSerializerOptions.OldSpec` or `MessagePackWriter.OldSpec` instead. +| `QeueueFormatter` | `QueueFormatter` +| `TaskUnitFormatter` | Store values instead of promises +| `TaskValueFormatter` | Store values instead of promises +| `ThreeDimentionalArrayFormatter` | `ThreeDimensionalArrayFormatter` +| `TwoDimentionalArrayFormatter` | `TwoDimensionalArrayFormatter` +| `ValueTaskFormatter` | Store values instead of promises + +A few formatters that remain have changed to remove mutable properties where those formatters may be exposed +as public static instances. This helps to avoid malfunctions when one MessagePack user changes a static setting +to suit their need but in a way that conflicts with another MessagePack user within the same process. + +#### `TypelessFormatter` changes + +The `TypelessFormatter.BindToType` static property has been removed. +If you were using this property, you can find equivalent functionality in the virtual `Type MessagePackSerializerOptions.LoadType(string typeName)` method. The `TypelessFormatter` will call this method on +the `MessagePackSerializerOptions` instance passed to it during deserialization. + +For example, you can override this virtual method in your own derived type: + +```cs +class LoadTypeCustomizedOptions : MessagePackSerializerOptions +{ + internal LoadTypeCustomizedOptions(MessagePackSerializerOptions copyFrom) + : base(copyFrom) + { + } + + internal LoadTypeCustomizedOptions(IFormatterResolver resolver) + : base(resolver) + { + } + + public override Type LoadType(string typeName) + { + Type type = base.LoadType(typeName); + if (type == null) + { + // custom logic here + } + + return type; + } +} +``` + +You can then instantiate this options type and pass it to your deserializer: + +```cs +var options = new LoadTypeCustomizedOptions(MessagePackSerializerOptions.Standard); +T value = MessagePackSerializer.Deserialize(sequence, options); +``` + +### Custom formatters + +If you have written a custom `IMessagePackFormatter` implementation you will have to adapt to the interface changes and APIs used to implement such a class. + +The interface has been changed as described here: + +```diff + public interface IMessagePackFormatter : IMessagePackFormatter + { +- int Serialize(ref byte[] bytes, int offset, T value, IFormatterResolver formatterResolver); ++ void Serialize(ref MessagePackWriter writer, T value, MessagePackSerializerOptions options); +- T Deserialize(byte[] bytes, int offset, IFormatterResolver formatterResolver, out int readSize); ++ T Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options); + } +``` + +Notice the simpler method signature for each method. +You no longer have to deal with raw arrays and offsets. +The `MessagePackBinary` static class from v1.x that a formatter used to write msgpack codes is replaced with `MessagePackWriter` and `MessagePackReader`. +These two structs include the APIs to write and read msgpack, and they manage the underlying buffers so you no longer need to. + +Consider the following v1.x formatter for the `Int16` type: + +```cs +class NullableInt16Formatter : IMessagePackFormatter +{ + public int Serialize(ref byte[] bytes, int offset, Int16? value, IFormatterResolver formatterResolver) + { + if (value == null) + { + return MessagePackBinary.WriteNil(ref bytes, offset); + } + else + { + return MessagePackBinary.WriteInt16(ref bytes, offset, value.Value); + } + } + + public Int16? Deserialize(byte[] bytes, int offset, IFormatterResolver formatterResolver, out int readSize) + { + if (MessagePackBinary.IsNil(bytes, offset)) + { + readSize = 1; + return null; + } + else + { + return MessagePackBinary.ReadInt16(bytes, offset, out readSize); + } + } +} +``` + +After migration for v2.x, it looks like this: + +```cs +class NullableInt16Formatter : IMessagePackFormatter +{ + public void Serialize(ref MessagePackWriter writer, Int16? value, MessagePackSerializerOptions options) + { + if (value == null) + { + writer.WriteNil(); + } + else + { + writer.Write(value.Value); + } + } + + public Int16? Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) + { + if (reader.TryReadNil()) + { + return default; + } + else + { + return reader.ReadInt16(); + } + } +} +``` + +Notice the structure is very similar, but arrays and offsets are no longer necessary. +The underlying msgpack format is unchanged, allowing code to be upgraded to v2.x while maintaining +compatibility with a file or network party that uses MessagePack v1.x. + +#### Subtle change in method naming + +When writing integers, the method name pattern has changed such that although your v1.x->v2.0 code will compile +it may produce slightly different (and less efficient) msgpack binary than before. Here is the translation table: + +|v1.x|v2.x| +|--|--| +|`MessagePackBinary.WriteMapHeaderForceMap32Block`|(removed) +|`MessagePackBinary.WriteArrayHeaderForceArray32Block`|(removed) +|`MessagePackBinary.WriteByteForceByteBlock`|`MessagePackWriter.WriteUInt8(byte)` +|`MessagePackBinary.WriteSByteForceSByteBlock`|`MessagePackWriter.WriteInt8(sbyte)` +|`MessagePackBinary.WriteInt16ForceInt16Block`|`MessagePackWriter.WriteInt16(short)` +|`MessagePackBinary.WriteInt64ForceInt64Block`|`MessagePackWriter.WriteInt64(long)` +|`MessagePackBinary.MessagePackBinary.WriteInt32ForceInt32Block`|`MessagePackWriter.WriteInt32(int)` +|`MessagePackBinary.WriteUInt16ForceUInt16Block`|`MessagePackWriter.WriteUInt16(ushort)` +|`MessagePackBinary.WriteUInt32ForceUInt32Block`|`MessagePackWriter.WriteUInt32(uint)` +|`MessagePackBinary.WriteUInt64ForceUInt64Block`|`MessagePackWriter.WriteUInt64(ulong)` +|`MessagePackBinary.WriteStringForceStr32Block`|(removed) +|`MessagePackBinary.WriteExtensionFormatHeaderForceExt32Block`|(removed) +|`MessagePackBinary.WriteMapHeader`|`MessagePackWriter.WriteMapHeader` +|`MessagePackBinary.WriteArrayHeader`|`MessagePackWriter.WriteArrayHeader` +|`MessagePackBinary.WriteByte`|`MessagePackWriter.Write(byte)` +|`MessagePackBinary.WriteBytes`|`MessagePackWriter.Write(byte[])` +|`MessagePackBinary.WriteSByte`|`MessagePackWriter.Write(sbyte)` +|`MessagePackBinary.WriteSingle`|`MessagePackWriter.Write(float)` +|`MessagePackBinary.WriteDouble`|`MessagePackWriter.Write(double)` +|`MessagePackBinary.WriteInt16`|`MessagePackWriter.Write(short)` +|`MessagePackBinary.WriteInt32`|`MessagePackWriter.Write(int)` +|`MessagePackBinary.WriteInt64`|`MessagePackWriter.Write(long)` +|`MessagePackBinary.WriteUInt16`|`MessagePackWriter.Write(ushort)` +|`MessagePackBinary.WriteUInt32`|`MessagePackWriter.Write(uint)` +|`MessagePackBinary.WriteUInt64`|`MessagePackWriter.Write(ulong)` +|`MessagePackBinary.WriteChar`|`MessagePackWriter.Write(char)` +|`MessagePackBinary.WriteStringBytes`|`MessagePackWriter.WriteString(ReadOnlySpan)` +|`MessagePackBinary.WriteString`|`MessagePackWriter.Write(string)` +|`MessagePackBinary.WriteExtensionFormatHeader`|`MessagePackWriter.WriteExtensionFormatHeader` +|`MessagePackBinary.WriteExtensionFormat`|`MessagePackWriter.WriteExtensionFormat` +|`MessagePackBinary.WriteDateTime`|`MessagePackWriter.Write(DateTime)` ([notes](#DateTime)) + +The essence here is that you can typically just call `MessagePackWriter.Write(*)` +for primitive types and the most efficient msgpack binary will be written out. +You only should call the explicit `WriteX(x)` methods if you need to force a particular +(fixed length) format of a value to be written out. + +As for the integer *reading* methods, these are much more interchangeable than in v1.x. +You can call *any* `ReadInt*` or `ReadUInt*` method and it will successfully read an integer +value and fit it into the desired return type so long as the value doesn't overflow. +So for example you can call `Write(byte)` and later read the value with `ReadInt32()`. +You can even call `Write(long)` and later read it with `ReadByte()` and it will work +so long as the actual value fits inside a `byte`. +An `OverflowException` is thrown if the integer value exceeds the max or min value +that can be stored by the required return type. + +## Behavioral changes + +### DateTime + +When writing out `DateTime` v1.x would *always* call `DateTime.ToUniversalTime()` before serializing the value. +In v2.x [we only call this method if `DateTime.Kind == DateTimeKind.Local`](https://github.com/neuecc/MessagePack-CSharp/pull/520/files). +The impact of this is that if you were writing `DateTimeKind.Unspecified` the serialized value will no longer be changed +under some unjustified assumption that the underlying value was `Local`. +Your should specify `DateTimeKind` explicitly for all your `DateTime` values. +When upgrading to MessagePack v2.x this is a breaking change if your `Unspecified` values actually represented the `Local` +time zone and needed the conversion. diff --git a/doc/migrating_v2-v3.md b/doc/migrating_v2-v3.md new file mode 100644 index 000000000..9d3569470 --- /dev/null +++ b/doc/migrating_v2-v3.md @@ -0,0 +1,34 @@ +# Migrating from MessagePack v2 to v3 + +The most significant change in v3 is that AOT source generation of formatters is on by default. +These new formatters are comparable to the dynamic formatters generated at runtime in prior versions, but they have some limitations not shared by their dynamically generated counterparts. +These limitation and other migration requirements and considerations are enumerated below or are called out by analyzers. + +The `MessagePackAnalyzer` nuget package, which was optional in v2, is now a nuget dependency, which means diagnostics may appear in your compilation after upgrading to v3 that you have not seen before. +v3 adds many new diagnostic providers to the set of analyzers as well, with general help and specific help for handling the new source generated formatters. + +## Breaking Changes + +- `MessagePackAnalyzer.json` is no longer used to configure the analyzer. + Use `GeneratedMessagePackResolverAttribute`, `MessagePackKnownFormatterAttribute` and `MessagePackAssumedFormattableAttribute` instead. +- The `mpc` CLI tool is no longer used to generate ahead-of-time (AOT) formatters and resolver. + AOT code generation is "on by default" in v3 courtesy of our roslyn source generator. +- Custom implementations of `IMessagePackFormatter` should be `internal` for automatic inclusion in our source generated resolver. +- Types annotated with `[MessagePackObject]` should be declared as `partial` to grant the source generated formatter access to private/protected members, when applicable. +- Unity users: + - Use NuGetForUnity to acquire the `MessagePack` nuget package instead of acquiring source code via the .zip file on our Releases page. + - Unity 2021.3 is no longer supported. The minimum required version is 2022.3.12f1. + +## Adapting to breaking changes + +### Migrate your `MessagePackAnalyzer.json` file + +1. Add `[assembly: MessagePackAssumedFormattable(typeof(MyType1))]` to your project for each type that appears inside your `MessagePackAnalyzer.json` file. +1. Delete the `MessagePackAnalyzer.json` file. + +### Migrate from `mpc` + +1. Remove any scripts that invoked `mpc` from your build. +1. Follow the instructions in [the AOT section of the README](../README.md#aot) to create a source generated resolver (with formatters). + +Be sure to build with .NET SDK 6.0 or later. diff --git a/doc/migration.md b/doc/migration.md index 30bd34e47..a815c74f1 100644 --- a/doc/migration.md +++ b/doc/migration.md @@ -1,393 +1,4 @@ -# Migrating from MessagePack v1.x to MessagePack v2.x +# Migration instructions -MessagePack 2.0 contains many breaking changes since the 1.x versions. -These include both binary and source breaking changes, meaning you may need to update your source code as well as recompile against the 2.x version. - -The v1.x version will still be serviced for security fixes, but new features will tend to only be offered in the 2.x versions. - -Update your package references from the 1.x version you use to the 2.x version. If your project compiles, you may be done. -Otherwise work through each compiler error. Some common ones you may face are listed below with suggested fixes. - -If you own an application that has a mix of MessagePack consumers and not all of them can be upgraded to v2.x at once, you can offer both MessagePack v1.x and v2.x assemblies with your application so that each user can find the one it needs. [Here is a sample](https://github.com/AArnott/MessagePackDualVersions). - -## API changes - -### MessagePackSerializerOptions - -A new `MessagePackSerializerOptions` class becomes a first class citizen in this library. -It encapsulates the `IFormatterResolver` that used to be passed around by itself. -It also includes several other settings that may influence how `MessagePackSerializerOptions` or some of the -formatters may operate. - -Because this new class tends to get saved to public static properties, it is immutable to ensure it can be shared safely. -Each property `Foo` on the class includes a `WithFoo` method which clones the instance and returns the new instance with just that one property changed. - -To support this new options class and avoid unnecessary allocations from the copy-and-mutate methods, many of the popular resolvers now expose a public static `Options` property with the resolver preset to itself. So for example, you may use: - -```cs -var msgpack = MessagePackSerializer.Serialize(objectGraph, StandardResolverAllowPrivate.Options); -var deserializedGraph = MessagePackSerializer.Deserialize(msgpack, StandardResolverAllowPrivate.Options); -``` - -If you want to combine a particular resolver with other options changes (e.g. enabling LZ4 compression), you may do that too: - -```cs -var options = StandardResolverAllowPrivate.Options.WithCompression(MessagePackCompression.Lz4BlockArray); -var msgpack = MessagePackSerializer.Serialize(objectGraph, options); -var deserializedGraph = MessagePackSerializer.Deserialize(msgpack, options); -``` - -An equivalent options instance can be created manually: - -```cs -var options = MessagePackSerializerOptions.Standard - .WithCompression(MessagePackCompression.Lz4BlockArray) - .WithResolver(StandardResolverAllowPrivate.Instance); -``` - -### MessagePackSerializer class - -#### Serialization - -Serializing object graphs to msgpack is now based on `IBufferWriter` instead of `ref byte[]`. -This allows for serializing very large object graphs without repeatedly allocating ever-larger arrays and copying the previously serialized msgpack bytes from the smaller buffer to the larger one. -`IBufferWriter` can direct the written msgpack bytes directly to a pipe, a file, or anywhere else you choose, allowing you to avoid a buffer copy within your own code as well. - -An `IBufferWriter` is always wrapped by the new `MessagePackWriter` struct. - -Many overloads of the `Serialize` method exist which ultimately all call the overload that accepts a `MessagePackWriter`. - -#### Deserialization - -Deserializing msgpack sequences is now much more flexible. -Instead of deserializing from `byte[]` or `ArraySegment` only, you can deserialize from any `ReadOnlyMemory` or `ReadOnlySequence` instance. - -`ReadOnlyMemory` is like `ArraySegment` but more friendly and can refer to contiguous memory anywhere including native pointers. You can pass a `byte[]` or `ArraySegment` in anywhere that `ReadOnlyMemory` is expected and C# will implicitly cast for you (without any buffer copying). - -`ReadOnlySequence` allows for deserialization from non-continguously allocated memory, enabling you to deserialize very large msgpack sequences without risking an `OutOfMemoryException` due simply to the inability to find large amounts of free contiguous memory. - -Many overloads of the `Deserialize` method exists which ultimately all call the overload that accepts a `MessagePackReader`. - -#### Deserializing from a Stream - -Deserializing from a `Stream` has changed from v1.x to v2.0. The `readStrict` parameter has been removed and in v2.x -the `MessagePackSerializer.Deserialize{Async}(Stream)` methods act as if `readStrict: false` in v1.x. -This works great and is the preferred API to use when the entire `Stream` is expected to contain exactly one -top-level messagepack structure that you want to deserialize. - -For performance reasons, the entire `Stream` is read into memory before deserialization begins. -If there is more data on the `Stream` than the messagepack structure to be deserialized, -the deserialization will ignore the excess data, but the excess data wouldn't be on the `Stream` -any more to be read later. - -If the `Stream` is seekable (that is, its `CanSeek` property returns `true`) then after deserialization -is complete the `Stream` will be repositioned to the first byte after the messagepack data structure -that was deserialized. This means you'll get the `Stream` back as you might expect it, but only after -you paid a perf cost of "reading" more data than was necessary to deserialize. - -If the `Stream` is *not* seekable (e.g. a network stream) or contains multiple top-level messagepack -data structures consecutively, MessagePack 2.0 adds a new, more performant way to read each -messagepack structure. It's analogous to v1.x's `readStrict: true` mode, but is much more performant. -It comes in the form of the new `MessagePackStreamReader` class, and can be easily used as follows: - -```cs -static async Task> DeserializeListFromStreamAsync(Stream stream, CancellationToken cancellationToken) -{ - var dataStructures = new List(); - using (var streamReader = new MessagePackStreamReader(stream)) - { - while (await streamReader.ReadAsync(cancellationToken) is ReadOnlySequence msgpack) - { - dataStructures.Add(MessagePackSerializer.Deserialize(msgpack, cancellationToken: cancellationToken)); - } - } - - return dataStructures; -} -``` - -#### Default behavior - -The `DefaultResolver` static property has been replaced with the `DefaultOptions` static property. -Just as with v1.x, in v2.x this static property influences how serialization occurs -when the value is not explicitly specified when invoking one of the `MessagePackSerializer` methods. - -**WARNING**: When developing a simple application where you control all MessagePack-related code it may be safe to rely on this mutable static to control behavior. -For all other libraries or multi-purpose applications that use `MessagePackSerializer` you should explicitly specify the `MessagePackSerializerOptions` to use with each method invocation to guarantee your code behaves as you expect even when sharing an `AppDomain` or process with other MessagePack users that may change this static property. - -#### Non-generic methods - -In v1.x non-generic methods for serialization/deserialization were exposed on the nested `MessagePackSerializer.NonGeneric` class. -In v2.x these overloads are moved to the `MessagePackSerializer` class itself. - -The `MessagePackSerializer.Typeless` nested class in v1.x remains in v2.x, but with a modified set of overloads. - -#### JSON converting methods - -In v1.x the `MessagePackSerializer` class exposed methods both to serialize an object graph to JSON, -as well as converting between msgpack and JSON. These two translations were very different but were mere overloads of each other. -In v2.x these methods have been renamed for clarity. -The methods `ConvertFromJson` and `ConvertToJson` translates between JSON and msgpack binary. -The method `SerializeToJson` translates an object graph to JSON. - -#### LZ4MessagePackSerializer - -The `LZ4MessagePackSerializer` class has been removed. -Instead, use `MessagePackSerializer` and pass in a `MessagePackSerializerOptions` with `WithCompression` set to `MessagePackCompression.Lz4Block` or `MessagePackCompression.Lz4BlockArray`. - -For example, make this change: - -```diff --byte[] buffer = LZ4MessagePackSerializer.Serialize("hi"); -+static readonly lz4Options = MessagePackSerializerOptions.Standard.WithCompression(MessagePackCompression.Lz4BlockArray); -+byte[] buffer = MessagePackSerializer.Serialize("hi", lz4Options); -``` - -`Lz4Block` is same as v1 LZ4MessagePackSerializer. `Lz4BlockArray` is new compression mode of v2. Regardless of which Lz4 option is set at the deserialization, both data can be deserialized. For example, when the option is `Lz4BlockArray`, binary data of both `Lz4Block` and `Lz4BlockArray` can be deserialized. - -### Thrown exceptions - -In v1.x any exception thrown during serialization or deserialization was uncaught and propagated to the application. -In v2.x all exceptions are caught by the `MessagePackSerializer` and rethrown as an inner exception of `MessagePackSerializationException`. -This makes it easier to write code to catch exceptions during serialization since you can now catch just one specific type of exception. - -### Built-in resolvers - -The following resolvers have been *removed*: - -| Removed v1.x formatter | v2.x alternative | -|--|--| -| `UnsafeBinaryResolver` | `NativeDecimalResolver`, `NativeGuidResolver` - -#### CompositeResolver - -In v1.x the `CompositeResolver` type could only be used once and mutated a static property. -In v2.x the `CompositeResolver` type no longer mutates any statics and thus can be used safely by many callers that simply want to aggregate many formatters and/or resolvers into one resolver. This often removes the need for you to define your own `IFormatterResolver`. - -For example if you have written a custom formatter and want to use that in addition to what the `StandardResolver` offers, you can easily compose an aggregate resolver like this: - -```cs -var resolver = CompositeResolver.Create( - new IMessagePackFormatter[] { MyCustomFormatter.Instance }, - new IFormatterResolver[] { StandardResolver.Instance } -); -var options = MessagePackSerializerOptions.Standard.WithResolver(resolver); -var msgpack = MessagePackSerializer.Serialize(objectGraph, options); -var deserializedGraph = MessagePackSerializer.Deserialize(msgpack, options); -``` - -### Built-in formatters - -The following formatters have been *removed*: - -| Removed v1.x formatter | v2.x alternative | -|--|--| -| `BinaryDecimalFormatter` | `NativeDecimalFormatter` -| `BinaryGuidFormatter` | `NativeGuidFormatter` -| `FourDimentionalArrayFormatter` | `FourDimensionalArrayFormatter` -| `OldSpecBinaryFormatter` | Use `MessagePackSerializerOptions.OldSpec` or `MessagePackWriter.OldSpec` instead. -| `OldSpecStringFormatter` | Use `MessagePackSerializerOptions.OldSpec` or `MessagePackWriter.OldSpec` instead. -| `QeueueFormatter` | `QueueFormatter` -| `TaskUnitFormatter` | Store values instead of promises -| `TaskValueFormatter` | Store values instead of promises -| `ThreeDimentionalArrayFormatter` | `ThreeDimensionalArrayFormatter` -| `TwoDimentionalArrayFormatter` | `TwoDimensionalArrayFormatter` -| `ValueTaskFormatter` | Store values instead of promises - -A few formatters that remain have changed to remove mutable properties where those formatters may be exposed -as public static instances. This helps to avoid malfunctions when one MessagePack user changes a static setting -to suit their need but in a way that conflicts with another MessagePack user within the same process. - -#### `TypelessFormatter` changes - -The `TypelessFormatter.BindToType` static property has been removed. -If you were using this property, you can find equivalent functionality in the virtual `Type MessagePackSerializerOptions.LoadType(string typeName)` method. The `TypelessFormatter` will call this method on -the `MessagePackSerializerOptions` instance passed to it during deserialization. - -For example, you can override this virtual method in your own derived type: - -```cs -class LoadTypeCustomizedOptions : MessagePackSerializerOptions -{ - internal LoadTypeCustomizedOptions(MessagePackSerializerOptions copyFrom) - : base(copyFrom) - { - } - - internal LoadTypeCustomizedOptions(IFormatterResolver resolver) - : base(resolver) - { - } - - public override Type LoadType(string typeName) - { - Type type = base.LoadType(typeName); - if (type == null) - { - // custom logic here - } - - return type; - } -} -``` - -You can then instantiate this options type and pass it to your deserializer: - -```cs -var options = new LoadTypeCustomizedOptions(MessagePackSerializerOptions.Standard); -T value = MessagePackSerializer.Deserialize(sequence, options); -``` - -### Custom formatters - -If you have written a custom `IMessagePackFormatter` implementation you will have to adapt to the interface changes and APIs used to implement such a class. - -The interface has been changed as described here: - -```diff - public interface IMessagePackFormatter : IMessagePackFormatter - { -- int Serialize(ref byte[] bytes, int offset, T value, IFormatterResolver formatterResolver); -+ void Serialize(ref MessagePackWriter writer, T value, MessagePackSerializerOptions options); -- T Deserialize(byte[] bytes, int offset, IFormatterResolver formatterResolver, out int readSize); -+ T Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options); - } -``` - -Notice the simpler method signature for each method. -You no longer have to deal with raw arrays and offsets. -The `MessagePackBinary` static class from v1.x that a formatter used to write msgpack codes is replaced with `MessagePackWriter` and `MessagePackReader`. -These two structs include the APIs to write and read msgpack, and they manage the underlying buffers so you no longer need to. - -Consider the following v1.x formatter for the `Int16` type: - -```cs -class NullableInt16Formatter : IMessagePackFormatter -{ - public int Serialize(ref byte[] bytes, int offset, Int16? value, IFormatterResolver formatterResolver) - { - if (value == null) - { - return MessagePackBinary.WriteNil(ref bytes, offset); - } - else - { - return MessagePackBinary.WriteInt16(ref bytes, offset, value.Value); - } - } - - public Int16? Deserialize(byte[] bytes, int offset, IFormatterResolver formatterResolver, out int readSize) - { - if (MessagePackBinary.IsNil(bytes, offset)) - { - readSize = 1; - return null; - } - else - { - return MessagePackBinary.ReadInt16(bytes, offset, out readSize); - } - } -} -``` - -After migration for v2.x, it looks like this: - -```cs -class NullableInt16Formatter : IMessagePackFormatter -{ - public void Serialize(ref MessagePackWriter writer, Int16? value, MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - } - else - { - writer.Write(value.Value); - } - } - - public Int16? Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return default; - } - else - { - return reader.ReadInt16(); - } - } -} -``` - -Notice the structure is very similar, but arrays and offsets are no longer necessary. -The underlying msgpack format is unchanged, allowing code to be upgraded to v2.x while maintaining -compatibility with a file or network party that uses MessagePack v1.x. - -#### Subtle change in method naming - -When writing integers, the method name pattern has changed such that although your v1.x->v2.0 code will compile -it may produce slightly different (and less efficient) msgpack binary than before. Here is the translation table: - -|v1.x|v2.x| -|--|--| -|`MessagePackBinary.WriteMapHeaderForceMap32Block`|(removed) -|`MessagePackBinary.WriteArrayHeaderForceArray32Block`|(removed) -|`MessagePackBinary.WriteByteForceByteBlock`|`MessagePackWriter.WriteUInt8(byte)` -|`MessagePackBinary.WriteSByteForceSByteBlock`|`MessagePackWriter.WriteInt8(sbyte)` -|`MessagePackBinary.WriteInt16ForceInt16Block`|`MessagePackWriter.WriteInt16(short)` -|`MessagePackBinary.WriteInt64ForceInt64Block`|`MessagePackWriter.WriteInt64(long)` -|`MessagePackBinary.MessagePackBinary.WriteInt32ForceInt32Block`|`MessagePackWriter.WriteInt32(int)` -|`MessagePackBinary.WriteUInt16ForceUInt16Block`|`MessagePackWriter.WriteUInt16(ushort)` -|`MessagePackBinary.WriteUInt32ForceUInt32Block`|`MessagePackWriter.WriteUInt32(uint)` -|`MessagePackBinary.WriteUInt64ForceUInt64Block`|`MessagePackWriter.WriteUInt64(ulong)` -|`MessagePackBinary.WriteStringForceStr32Block`|(removed) -|`MessagePackBinary.WriteExtensionFormatHeaderForceExt32Block`|(removed) -|`MessagePackBinary.WriteMapHeader`|`MessagePackWriter.WriteMapHeader` -|`MessagePackBinary.WriteArrayHeader`|`MessagePackWriter.WriteArrayHeader` -|`MessagePackBinary.WriteByte`|`MessagePackWriter.Write(byte)` -|`MessagePackBinary.WriteBytes`|`MessagePackWriter.Write(byte[])` -|`MessagePackBinary.WriteSByte`|`MessagePackWriter.Write(sbyte)` -|`MessagePackBinary.WriteSingle`|`MessagePackWriter.Write(float)` -|`MessagePackBinary.WriteDouble`|`MessagePackWriter.Write(double)` -|`MessagePackBinary.WriteInt16`|`MessagePackWriter.Write(short)` -|`MessagePackBinary.WriteInt32`|`MessagePackWriter.Write(int)` -|`MessagePackBinary.WriteInt64`|`MessagePackWriter.Write(long)` -|`MessagePackBinary.WriteUInt16`|`MessagePackWriter.Write(ushort)` -|`MessagePackBinary.WriteUInt32`|`MessagePackWriter.Write(uint)` -|`MessagePackBinary.WriteUInt64`|`MessagePackWriter.Write(ulong)` -|`MessagePackBinary.WriteChar`|`MessagePackWriter.Write(char)` -|`MessagePackBinary.WriteStringBytes`|`MessagePackWriter.WriteString(ReadOnlySpan)` -|`MessagePackBinary.WriteString`|`MessagePackWriter.Write(string)` -|`MessagePackBinary.WriteExtensionFormatHeader`|`MessagePackWriter.WriteExtensionFormatHeader` -|`MessagePackBinary.WriteExtensionFormat`|`MessagePackWriter.WriteExtensionFormat` -|`MessagePackBinary.WriteDateTime`|`MessagePackWriter.Write(DateTime)` ([notes](#DateTime)) - -The essence here is that you can typically just call `MessagePackWriter.Write(*)` -for primitive types and the most efficient msgpack binary will be written out. -You only should call the explicit `WriteX(x)` methods if you need to force a particular -(fixed length) format of a value to be written out. - -As for the integer *reading* methods, these are much more interchangeable than in v1.x. -You can call *any* `ReadInt*` or `ReadUInt*` method and it will successfully read an integer -value and fit it into the desired return type so long as the value doesn't overflow. -So for example you can call `Write(byte)` and later read the value with `ReadInt32()`. -You can even call `Write(long)` and later read it with `ReadByte()` and it will work -so long as the actual value fits inside a `byte`. -An `OverflowException` is thrown if the integer value exceeds the max or min value -that can be stored by the required return type. - -## Behavioral changes - -### DateTime - -When writing out `DateTime` v1.x would *always* call `DateTime.ToUniversalTime()` before serializing the value. -In v2.x [we only call this method if `DateTime.Kind == DateTimeKind.Local`](https://github.com/neuecc/MessagePack-CSharp/pull/520/files). -The impact of this is that if you were writing `DateTimeKind.Unspecified` the serialized value will no longer be changed -under some unjustified assumption that the underlying value was `Local`. -Your should specify `DateTimeKind` explicitly for all your `DateTime` values. -When upgrading to MessagePack v2.x this is a breaking change if your `Unspecified` values actually represented the `Local` -time zone and needed the conversion. +- [Migrating from MessagePack v1 to v2](migrating_v1-v2.md) +- [Migrating from MessagePack v2 to v3](migrating_v2-v3.md) diff --git a/doc/msbuildtask.md b/doc/msbuildtask.md deleted file mode 100644 index 5b9d75c86..000000000 --- a/doc/msbuildtask.md +++ /dev/null @@ -1,59 +0,0 @@ -# MessagePack Compiler via MSBuild Task - -Cold startup performance and AOT environments can benefit by pre-compiling the specialized code -for serializing and deserializing your custom types. - -Install the `MessagePack.MSBuild.Tasks` NuGet package in your project: - [![NuGet](https://img.shields.io/nuget/v/MessagePack.MSBuild.Tasks.svg)](https://www.nuget.org/packages/MessagePack.MSBuild.Tasks) - -This package automatically gets the MessagePack Compiler (mpc) to run during the build to produce a source file in the intermediate directory and adds it to the compilation, consumable in the normal way: - -```cs -using System; -using MessagePack; -using MessagePack.Resolvers; - -class Program -{ - static void Main(string[] args) - { - var o = new SomeObject { SomeMember = "hi" }; - - var options = MessagePackSerializerOptions.Standard.WithResolver( - CompositeResolver.Create( - GeneratedResolver.Instance, - StandardResolver.Instance - )); - byte[] b = MessagePackSerializer.Serialize(o, options); - var o2 = MessagePackSerializer.Deserialize(b, options); - Console.WriteLine(o2.SomeMember); - } -} - -[MessagePackObject] -public class SomeObject -{ - [Key(0)] - public string SomeMember { get; set; } -} -``` - -## Customizations - -A few MSBuild properties can be set in your project to customize mpc: - -Property | Purpose | Default value ---|--|-- -`MessagePackGeneratedResolverNamespace` | The prefix for the namespace under which code will be generated. `.Formatters` is always appended to this value. | `MessagePack` -`MessagePackGeneratedResolverName` | The name of the generated type. | `GeneratedResolver` -`MessagePackGeneratedUsesMapMode` | A boolean value that indicates whether all formatters should use property maps instead of more compact arrays. | `false` - -For example you could add this xml to your project file to set each of the above properties (in this example, to their default values): - -```xml - - MessagePack - GeneratedResolver - false - -``` diff --git a/global.json b/global.json index c83e939e8..088f23e11 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.300", + "version": "9.0.100", "rollForward": "patch", "allowPrerelease": false } diff --git a/init.ps1 b/init.ps1 index ad3b4145c..d5909d736 100755 --- a/init.ps1 +++ b/init.ps1 @@ -28,6 +28,8 @@ No effect if -NoPrerequisites is specified. .PARAMETER NoRestore Skips the package restore step. +.PARAMETER NoToolRestore + Skips the dotnet tool restore step. .PARAMETER AccessToken An optional access token for authenticating to Azure Artifacts authenticated feeds. .PARAMETER Interactive @@ -46,6 +48,8 @@ Param ( [Parameter()] [switch]$NoRestore, [Parameter()] + [switch]$NoToolRestore, + [Parameter()] [string]$AccessToken, [Parameter()] [switch]$Interactive @@ -63,12 +67,6 @@ if (!$NoPrerequisites) { if ($LASTEXITCODE -eq 3010) { Exit 3010 } - - # The procdump tool and env var is required for dotnet test to collect hang/crash dumps of tests. - # But it only works on Windows. - if ($env:OS -eq 'Windows_NT') { - $EnvVars['PROCDUMP_PATH'] = & "$PSScriptRoot\azure-pipelines\Get-ProcDump.ps1" - } } # Workaround nuget credential provider bug that causes very unreliable package restores on Azure Pipelines @@ -79,13 +77,12 @@ Push-Location $PSScriptRoot try { $HeaderColor = 'Green' - if (!$NoRestore -and $PSCmdlet.ShouldProcess("NuGet packages", "Restore")) { - $RestoreArguments = @() - if ($Interactive) - { - $RestoreArguments += '--interactive' - } + $RestoreArguments = @() + if ($Interactive) { + $RestoreArguments += '--interactive' + } + if (!$NoRestore -and $PSCmdlet.ShouldProcess("NuGet packages", "Restore")) { Write-Host "Restoring NuGet packages" -ForegroundColor $HeaderColor dotnet restore @RestoreArguments if ($lastexitcode -ne 0) { @@ -93,6 +90,13 @@ try { } } + if (!$NoToolRestore -and $PSCmdlet.ShouldProcess("dotnet tool", "restore")) { + dotnet tool restore @RestoreArguments + if ($lastexitcode -ne 0) { + throw "Failure while restoring dotnet CLI tools." + } + } + & "$PSScriptRoot/tools/Set-EnvVars.ps1" -Variables $EnvVars -PrependPath $PrependPath | Out-Null } catch { diff --git a/nuget.config b/nuget.config index 2ed04eeb6..ba780d4f6 100644 --- a/nuget.config +++ b/nuget.config @@ -7,9 +7,20 @@ + + + + + + + + + + + diff --git a/prepare_release.ps1 b/prepare_release.ps1 deleted file mode 100644 index d5dc21d6f..000000000 --- a/prepare_release.ps1 +++ /dev/null @@ -1,14 +0,0 @@ -# Calculate the NPM package version, assuming the version change is in a new commit. -git commit --allow-empty -m "Dummy commit" -q -$NpmPackageVersion = (nbgv get-version -f json | ConvertFrom-Json).NpmPackageVersion -git reset --mixed HEAD~ -q - -# Stamp the version into the package.json file and commit. -pushd $PSScriptRoot/src/MessagePack.UnityClient/Assets/Scripts/MessagePack -npm version $NpmPackageVersion --no-git-tag-version --allow-same-version -git add package.json -popd -git commit -m "Stamp unity package version as $NpmPackageVersion" - -# Tag the release -nbgv tag diff --git a/sandbox/.editorconfig b/sandbox/.editorconfig new file mode 100644 index 000000000..c7bcd1010 --- /dev/null +++ b/sandbox/.editorconfig @@ -0,0 +1,4 @@ +[*.cs] + +# CA1062: Validate arguments of public methods +dotnet_diagnostic.CA1062.severity = silent diff --git a/sandbox/Directory.Build.props b/sandbox/Directory.Build.props index 68555510b..6bbc1b411 100644 --- a/sandbox/Directory.Build.props +++ b/sandbox/Directory.Build.props @@ -1,6 +1,7 @@ - + true false + diff --git a/sandbox/DynamicCodeDumper/DynamicCodeDumper.csproj b/sandbox/DynamicCodeDumper/DynamicCodeDumper.csproj index 8d1a7c7e2..4b3e91d14 100644 --- a/sandbox/DynamicCodeDumper/DynamicCodeDumper.csproj +++ b/sandbox/DynamicCodeDumper/DynamicCodeDumper.csproj @@ -7,150 +7,172 @@ $(DefineConstants);DYNAMICCODEDUMPER - + Code\Attributes.cs - + + Code\AnalyzerAttributes.cs + + Code\BufferWriter.cs - + Code\IMessagePackFormatter`1.cs - + Code\NullableFormatter.cs - + Code\DateTimeFormatters.cs - + Code\IFormatterResolver.cs - + Code\IMessagePackSerializationCallbackReceiver.cs - + Code\AutomataDictionary.cs - + + Code\AutomataKeyGen.cs + + Code\ThreadsafeTypeKeyHashTable.cs - + Code\ByteArrayStringHashTable.cs - + Code\CodeGenHelpers.cs - + Code\RuntimeTypeHandleEqualityComparer.cs - + Code\DynamicAssembly.cs - + + Code\DynamicAssemblyFactory.cs + + Code\ExpressionUtility.cs - + Code\FarmHash.cs - + Code\ILGeneratorExtensions.cs - + Code\ReflectionExtensions.cs - + Code\UnsafeMemory.cs - + Code\UnsafeMemory.Low.cs - + Code\ExtensionHeader.cs - + Code\ExtensionResult.cs - + Code\MessagePackEventSource.cs - + Code\MessagePackSerializerOptions.cs - + Code\MessagePackSecurity.cs - - Code\SipHash.cs - - + Code\MonoProtection.cs - + Code\HashCode.cs - + Code\BitOperations.cs - + Code\MessagePackCompression.cs - + Code\MessagePackReader.cs - + Code\MessagePackReader.Integers.cs - + Code\MessagePackWriter.cs - + + Code\MessagePackPrimitives.Writers.cs + + + Code\MessagePackPrimitives.Readers.cs + + + Code\MessagePackPrimitives.Readers.Integers.cs + + + Code\SkipClrVisibilityChecks.cs + + Code\SequencePool.cs - + Code\SequenceReader.cs - + Code\SequenceReaderExtensions.cs - + Code\SafeBitConverter.cs - + + Code\SipHash.cs + + Code\MessagePackCode.cs - + Code\MessagePackSerializationException.cs - + Code\Internal\DateTimeConstants.cs - + Code\Nil.cs - + Code\Utilities.cs - + Code\DynamicEnumResolver.cs - + Code\DynamicObjectResolver.cs - + Code\DynamicUnionResolver.cs - + Code\ResolverUtilities.cs - + Code\StringEncoding.cs - + Class1.cs + diff --git a/sandbox/DynamicCodeDumper/Program.cs b/sandbox/DynamicCodeDumper/Program.cs index d9ec30cb4..1d24bcc16 100644 --- a/sandbox/DynamicCodeDumper/Program.cs +++ b/sandbox/DynamicCodeDumper/Program.cs @@ -80,10 +80,10 @@ private static void Main(string[] args) } finally { - AssemblyBuilder a1 = DynamicObjectResolver.Instance.Save(); - AssemblyBuilder a2 = DynamicUnionResolver.Instance.Save(); - AssemblyBuilder a3 = DynamicEnumResolver.Instance.Save(); - AssemblyBuilder a4 = DynamicContractlessObjectResolver.Instance.Save(); + AssemblyBuilder? a1 = DynamicObjectResolver.Instance.Save(); + AssemblyBuilder? a2 = DynamicUnionResolver.Instance.Save(); + AssemblyBuilder? a3 = DynamicEnumResolver.Instance.Save(); + AssemblyBuilder? a4 = DynamicContractlessObjectResolver.Instance.Save(); ////var a5 = AutomataKeyGen.Save(); ////Verify(a5); @@ -467,3 +467,13 @@ public EntityBase() //// } ////} } + + +#pragma warning disable + +namespace MessagePack.Internal +{ + internal sealed class PreserveAttribute : System.Attribute + { + } +} diff --git a/sandbox/MessagePack.Internal/MessagePack.Internal.csproj b/sandbox/MessagePack.Internal/MessagePack.Internal.csproj index bc7d5d8d4..6aaf526a6 100644 --- a/sandbox/MessagePack.Internal/MessagePack.Internal.csproj +++ b/sandbox/MessagePack.Internal/MessagePack.Internal.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 enable $(DefineConstants);SPAN_BUILTIN;MESSAGEPACK_INTERNAL true @@ -16,23 +16,27 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/sandbox/PerfBenchmarkDotNet/DeserializeBenchmark.cs b/sandbox/PerfBenchmarkDotNet/DeserializeBenchmark.cs index 68af163a9..15127517e 100644 --- a/sandbox/PerfBenchmarkDotNet/DeserializeBenchmark.cs +++ b/sandbox/PerfBenchmarkDotNet/DeserializeBenchmark.cs @@ -96,6 +96,7 @@ public StringKeySerializerTarget Typeless_StringKey() return (StringKeySerializerTarget)newmsgpack.MessagePack.MessagePackSerializer.Typeless.Deserialize(typelessWithStringKeyObj); } +#if MsgPackCli [Benchmark] public IntKeySerializerTarget MsgPackCliMap() { @@ -107,7 +108,9 @@ public IntKeySerializerTarget MsgPackCliArray() { return arrayContext.GetSerializer().UnpackSingleObject(arrayObj); } +#endif +#if Protobuf [Benchmark] public IntKeySerializerTarget ProtobufNet() { @@ -116,7 +119,9 @@ public IntKeySerializerTarget ProtobufNet() return ProtoBuf.Serializer.Deserialize(ms); } } +#endif +#if Hyperion [Benchmark] public IntKeySerializerTarget Hyperion() { @@ -125,7 +130,9 @@ public IntKeySerializerTarget Hyperion() return hyperionSerializer.Deserialize(ms); } } +#endif +#if NewtonsoftJson [Benchmark] public IntKeySerializerTarget JsonNetString() { @@ -142,7 +149,9 @@ public IntKeySerializerTarget JsonNetStreamReader() return jsonSerialzier.Deserialize(jr); } } +#endif +#if Jil [Benchmark] public IntKeySerializerTarget JilString() { @@ -158,9 +167,6 @@ public IntKeySerializerTarget JilStreamReader() return Jil.JSON.Deserialize(sr); } } +#endif } } - -#pragma warning restore SA1200 // Using directives should be placed correctly -#pragma warning restore SA1403 // File may only contain a single namespace - diff --git a/sandbox/PerfBenchmarkDotNet/Lz4Benchmark.cs b/sandbox/PerfBenchmarkDotNet/Lz4Benchmark.cs index 8ac52e8ec..2d476656e 100644 --- a/sandbox/PerfBenchmarkDotNet/Lz4Benchmark.cs +++ b/sandbox/PerfBenchmarkDotNet/Lz4Benchmark.cs @@ -53,7 +53,3 @@ public void Setup() public StringKeySerializerTarget2[] DeserializeLz4BlockArray() => MessagePackSerializer.Deserialize(binLz4ContiguousBlock, lz4ContiguousBlockOptions); } } - -#pragma warning restore SA1200 // Using directives should be placed correctly -#pragma warning restore SA1403 // File may only contain a single namespace - diff --git a/sandbox/PerfBenchmarkDotNet/Lz4PrimitiveBenchmark.cs b/sandbox/PerfBenchmarkDotNet/Lz4PrimitiveBenchmark.cs index f2d8c47b7..c00cf7f0b 100644 --- a/sandbox/PerfBenchmarkDotNet/Lz4PrimitiveBenchmark.cs +++ b/sandbox/PerfBenchmarkDotNet/Lz4PrimitiveBenchmark.cs @@ -50,7 +50,3 @@ public void Setup() public int DeserializeLz4BlockArray() => MessagePackSerializer.Deserialize(binLz4ContiguousBlock, lz4ContiguousBlockOptions); } } - -#pragma warning restore SA1200 // Using directives should be placed correctly -#pragma warning restore SA1403 // File may only contain a single namespace - diff --git a/sandbox/PerfBenchmarkDotNet/MessagePackReaderBenchmark.cs b/sandbox/PerfBenchmarkDotNet/MessagePackReaderBenchmark.cs index b41e6258d..2ecceaffb 100644 --- a/sandbox/PerfBenchmarkDotNet/MessagePackReaderBenchmark.cs +++ b/sandbox/PerfBenchmarkDotNet/MessagePackReaderBenchmark.cs @@ -7,7 +7,6 @@ using System; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Configs; -using Nerdbank.Streams; namespace PerfBenchmarkDotNet { @@ -29,6 +28,19 @@ public void GlobalSetup() [Benchmark(OperationsPerInvoke = BufferLength)] [BenchmarkCategory("2.0")] + public void ReadBytePrimitiveInt32() + { + int offset = 0; + ReadOnlySpan buffer = this.buffer; + for (int i = 0; i < this.buffer.Length; i++) + { + newmsgpack::MessagePack.MessagePackPrimitives.TryReadInt32(buffer.Slice(offset), out int value, out var readSize); + offset += readSize; + } + } + + [Benchmark(OperationsPerInvoke = BufferLength, Baseline = true)] + [BenchmarkCategory("2.0")] public void ReadByte20() { var reader = new newmsgpack::MessagePack.MessagePackReader(this.buffer); diff --git a/sandbox/PerfBenchmarkDotNet/PerfBenchmarkDotNet.csproj b/sandbox/PerfBenchmarkDotNet/PerfBenchmarkDotNet.csproj index 4dbab7b25..26d20cfa3 100644 --- a/sandbox/PerfBenchmarkDotNet/PerfBenchmarkDotNet.csproj +++ b/sandbox/PerfBenchmarkDotNet/PerfBenchmarkDotNet.csproj @@ -1,10 +1,26 @@  Exe - net472;net7.0 + net472;net8.0 true true + + true + true + true + true + true + true + true + + $(DefineConstants);Jil + $(DefineConstants);MsgPackCli + $(DefineConstants);Protobuf + $(DefineConstants);ZeroFormatter + $(DefineConstants);NewtonsoftJson + $(DefineConstants);Hyperion + False @@ -17,13 +33,11 @@ - - diff --git a/sandbox/PerfBenchmarkDotNet/SerializeBenchmark.cs b/sandbox/PerfBenchmarkDotNet/SerializeBenchmark.cs index fc9a3a06d..b3301150a 100644 --- a/sandbox/PerfBenchmarkDotNet/SerializeBenchmark.cs +++ b/sandbox/PerfBenchmarkDotNet/SerializeBenchmark.cs @@ -22,7 +22,7 @@ public class SerializeBenchmark { private static MsgPack.Serialization.SerializationContext mapContext = new MsgPack.Serialization.SerializationContext { SerializationMethod = SerializationMethod.Map }; private static MsgPack.Serialization.SerializationContext arrayContext = new MsgPack.Serialization.SerializationContext { SerializationMethod = SerializationMethod.Array }; - private static JsonSerializer jsonSerialzier = new JsonSerializer(); + private static JsonSerializer jsonSerializer = new JsonSerializer(); private static Hyperion.Serializer hyperionSerializer = new Hyperion.Serializer(); private static newmsgpack::MessagePack.IFormatterResolver mpcGenFormatterResolver = new Resolver(new StringKeySerializerTargetFormatter_MpcGeneratedAutomata()); private static newmsgpack::MessagePack.IFormatterResolver mpcGenDictFormatterResolver = new Resolver(new StringKeySerializerTargetFormatter_MpcGeneratedDictionary()); @@ -59,6 +59,7 @@ public byte[] Typeless_StringKey() return newmsgpack.MessagePack.MessagePackSerializer.Typeless.Serialize(stringData); } +#if MsgPackCli [Benchmark] public byte[] MsgPackCliMap() { @@ -70,7 +71,9 @@ public byte[] MsgPackCliArray() { return arrayContext.GetSerializer().PackSingleObject(intData); } +#endif +#if Protobuf [Benchmark] public byte[] ProtobufNet() { @@ -80,7 +83,9 @@ public byte[] ProtobufNet() return ms.ToArray(); } } +#endif +#if Hyperion [Benchmark] public byte[] Hyperion() { @@ -90,13 +95,17 @@ public byte[] Hyperion() return ms.ToArray(); } } +#endif +#if ZeroFormatter [Benchmark] public byte[] ZeroFormatter() { return ZeroFormatterSerializer.Serialize(intData); } +#endif +#if NewtonsoftJson [Benchmark] public byte[] JsonNetString() { @@ -111,13 +120,15 @@ public byte[] JsonNetStreamWriter() using (var sr = new StreamWriter(ms, Encoding.UTF8)) using (var jr = new JsonTextWriter(sr)) { - jsonSerialzier.Serialize(jr, intData); + jsonSerializer.Serialize(jr, intData); } return ms.ToArray(); } } +#endif +#if Jil [Benchmark] public byte[] JilString() { @@ -137,6 +148,7 @@ public byte[] JilStreamWriter() return ms.ToArray(); } } +#endif } } diff --git a/sandbox/PerfBenchmarkDotNet/TypelessDeserializeBenchmark.cs b/sandbox/PerfBenchmarkDotNet/TypelessDeserializeBenchmark.cs index 8f6b23bd2..b6dfa5f50 100644 --- a/sandbox/PerfBenchmarkDotNet/TypelessDeserializeBenchmark.cs +++ b/sandbox/PerfBenchmarkDotNet/TypelessDeserializeBenchmark.cs @@ -73,7 +73,3 @@ public TypelessPrimitiveType MessagePackSerializer_Deserialize_TypelessContractl } } } - -#pragma warning restore SA1200 // Using directives should be placed correctly -#pragma warning restore SA1403 // File may only contain a single namespace - diff --git a/sandbox/PerfNetFramework/PerfNetFramework.csproj b/sandbox/PerfNetFramework/PerfNetFramework.csproj index e97936e7e..321dced72 100644 --- a/sandbox/PerfNetFramework/PerfNetFramework.csproj +++ b/sandbox/PerfNetFramework/PerfNetFramework.csproj @@ -1,7 +1,7 @@  Exe - net472;net7.0 + net472;net8.0 true diff --git a/sandbox/PerfNetFramework/README.md b/sandbox/PerfNetFramework/README.md index f91fa842c..8c791a766 100644 --- a/sandbox/PerfNetFramework/README.md +++ b/sandbox/PerfNetFramework/README.md @@ -10,7 +10,7 @@ When collecting ETL traces, use these settings in the Collect->Run dialog: | Setting | Value | |-------------|-------| -| Command | `dotnet run -c release -p .\sandbox\PerfNetFramework\ -f net472 --no-build` +| Command | `dotnet run -c release --project .\sandbox\PerfNetFramework\ -f net472 --no-build` | Current Dir | `d:\git\messagepack-csharp` (or wherever your enlistment is) | Additional Providers | `*MessagePack-Benchmark` | No V3.X NGen | Checked diff --git a/sandbox/Sandbox/Generated.cs b/sandbox/Sandbox/Generated.cs deleted file mode 100644 index 696478189..000000000 --- a/sandbox/Sandbox/Generated.cs +++ /dev/null @@ -1,4481 +0,0 @@ -// -// THIS (.cs) FILE IS GENERATED BY MPC(MessagePack-CSharp). DO NOT CHANGE IT. -// - -#pragma warning disable 618 -#pragma warning disable 612 -#pragma warning disable 414 -#pragma warning disable 168 -#pragma warning disable CS1591 // document public APIs - -#pragma warning disable SA1312 // Variable names should begin with lower-case letter -#pragma warning disable SA1649 // File name should match first type name - -namespace MessagePack.Resolvers -{ - public class GeneratedResolver : global::MessagePack.IFormatterResolver - { - public static readonly global::MessagePack.IFormatterResolver Instance = new GeneratedResolver(); - - private GeneratedResolver() - { - } - - public global::MessagePack.Formatters.IMessagePackFormatter GetFormatter() - { - return FormatterCache.Formatter; - } - - private static class FormatterCache - { - internal static readonly global::MessagePack.Formatters.IMessagePackFormatter Formatter; - - static FormatterCache() - { - var f = GeneratedResolverGetFormatterHelper.GetFormatter(typeof(T)); - if (f != null) - { - Formatter = (global::MessagePack.Formatters.IMessagePackFormatter)f; - } - } - } - } - - internal static class GeneratedResolverGetFormatterHelper - { - private static readonly global::System.Collections.Generic.Dictionary lookup; - - static GeneratedResolverGetFormatterHelper() - { - lookup = new global::System.Collections.Generic.Dictionary(72) - { - { typeof(global::GlobalMyEnum[,]), 0 }, - { typeof(global::GlobalMyEnum[]), 1 }, - { typeof(global::QuestMessageBody[]), 2 }, - { typeof(global::System.Collections.Generic.IDictionary), 3 }, - { typeof(global::System.Collections.Generic.IList), 4 }, - { typeof(int[,,,]), 5 }, - { typeof(int[,,]), 6 }, - { typeof(int[,]), 7 }, - { typeof(global::GlobalMyEnum), 8 }, - { typeof(global::SharedData.ByteEnum), 9 }, - { typeof(global::IMessageBody), 10 }, - { typeof(global::SharedData.IIVersioningUnion), 11 }, - { typeof(global::SharedData.IUnionChecker), 12 }, - { typeof(global::SharedData.IUnionChecker2), 13 }, - { typeof(global::SharedData.IUnionSample), 14 }, - { typeof(global::SharedData.RootUnionType), 15 }, - { typeof(global::Abcdefg.Efcdigjl.Ateatatea.Hgfagfafgad.TnonodsfarnoiuAtatqaga), 16 }, - { typeof(global::ArrayTestTest), 17 }, - { typeof(global::ComplexModel), 18 }, - { typeof(global::GlobalMan), 19 }, - { typeof(global::Message), 20 }, - { typeof(global::MessagePackFormatterFieldUser), 21 }, - { typeof(global::PerfBenchmarkDotNet.StringKeySerializerTarget), 22 }, - { typeof(global::QuestMessageBody), 23 }, - { typeof(global::SharedData.ArrayOptimizeClass), 24 }, - { typeof(global::SharedData.BarClass), 25 }, - { typeof(global::SharedData.Callback1), 26 }, - { typeof(global::SharedData.Callback1_2), 27 }, - { typeof(global::SharedData.Callback2), 28 }, - { typeof(global::SharedData.Callback2_2), 29 }, - { typeof(global::SharedData.DefaultValueIntKeyClassWithExplicitConstructor), 30 }, - { typeof(global::SharedData.DefaultValueIntKeyClassWithoutExplicitConstructor), 31 }, - { typeof(global::SharedData.DefaultValueIntKeyStructWithExplicitConstructor), 32 }, - { typeof(global::SharedData.DefaultValueStringKeyClassWithExplicitConstructor), 33 }, - { typeof(global::SharedData.DefaultValueStringKeyClassWithoutExplicitConstructor), 34 }, - { typeof(global::SharedData.DefaultValueStringKeyStructWithExplicitConstructor), 35 }, - { typeof(global::SharedData.Empty1), 36 }, - { typeof(global::SharedData.Empty2), 37 }, - { typeof(global::SharedData.EmptyClass), 38 }, - { typeof(global::SharedData.EmptyStruct), 39 }, - { typeof(global::SharedData.FirstSimpleData), 40 }, - { typeof(global::SharedData.FooClass), 41 }, - { typeof(global::SharedData.HolderV0), 42 }, - { typeof(global::SharedData.HolderV1), 43 }, - { typeof(global::SharedData.HolderV2), 44 }, - { typeof(global::SharedData.MyClass), 45 }, - { typeof(global::SharedData.MySubUnion1), 46 }, - { typeof(global::SharedData.MySubUnion2), 47 }, - { typeof(global::SharedData.MySubUnion3), 48 }, - { typeof(global::SharedData.MySubUnion4), 49 }, - { typeof(global::SharedData.NestParent.NestContract), 50 }, - { typeof(global::SharedData.NonEmpty1), 51 }, - { typeof(global::SharedData.NonEmpty2), 52 }, - { typeof(global::SharedData.SimpleIntKeyData), 53 }, - { typeof(global::SharedData.SimpleStringKeyData), 54 }, - { typeof(global::SharedData.SimpleStructIntKeyData), 55 }, - { typeof(global::SharedData.SimpleStructStringKeyData), 56 }, - { typeof(global::SharedData.SubUnionType1), 57 }, - { typeof(global::SharedData.SubUnionType2), 58 }, - { typeof(global::SharedData.UnVersionBlockTest), 59 }, - { typeof(global::SharedData.Vector2), 60 }, - { typeof(global::SharedData.Vector3Like), 61 }, - { typeof(global::SharedData.VectorLike2), 62 }, - { typeof(global::SharedData.Version0), 63 }, - { typeof(global::SharedData.Version1), 64 }, - { typeof(global::SharedData.Version2), 65 }, - { typeof(global::SharedData.VersionBlockTest), 66 }, - { typeof(global::SharedData.VersioningUnion), 67 }, - { typeof(global::SharedData.WithIndexer), 68 }, - { typeof(global::SimpleModel), 69 }, - { typeof(global::StampMessageBody), 70 }, - { typeof(global::TextMessageBody), 71 }, - }; - } - - internal static object GetFormatter(global::System.Type t) - { - int key; - if (!lookup.TryGetValue(t, out key)) - { - return null; - } - - switch (key) - { - case 0: return new global::MessagePack.Formatters.TwoDimensionalArrayFormatter(); - case 1: return new global::MessagePack.Formatters.ArrayFormatter(); - case 2: return new global::MessagePack.Formatters.ArrayFormatter(); - case 3: return new global::MessagePack.Formatters.InterfaceDictionaryFormatter(); - case 4: return new global::MessagePack.Formatters.InterfaceListFormatter2(); - case 5: return new global::MessagePack.Formatters.FourDimensionalArrayFormatter(); - case 6: return new global::MessagePack.Formatters.ThreeDimensionalArrayFormatter(); - case 7: return new global::MessagePack.Formatters.TwoDimensionalArrayFormatter(); - case 8: return new MessagePack.Formatters.GlobalMyEnumFormatter(); - case 9: return new MessagePack.Formatters.SharedData.ByteEnumFormatter(); - case 10: return new MessagePack.Formatters.IMessageBodyFormatter(); - case 11: return new MessagePack.Formatters.SharedData.IIVersioningUnionFormatter(); - case 12: return new MessagePack.Formatters.SharedData.IUnionCheckerFormatter(); - case 13: return new MessagePack.Formatters.SharedData.IUnionChecker2Formatter(); - case 14: return new MessagePack.Formatters.SharedData.IUnionSampleFormatter(); - case 15: return new MessagePack.Formatters.SharedData.RootUnionTypeFormatter(); - case 16: return new MessagePack.Formatters.Abcdefg.Efcdigjl.Ateatatea.Hgfagfafgad.TnonodsfarnoiuAtatqagaFormatter(); - case 17: return new MessagePack.Formatters.ArrayTestTestFormatter(); - case 18: return new MessagePack.Formatters.ComplexModelFormatter(); - case 19: return new MessagePack.Formatters.GlobalManFormatter(); - case 20: return new MessagePack.Formatters.MessageFormatter(); - case 21: return new MessagePack.Formatters.MessagePackFormatterFieldUserFormatter(); - case 22: return new MessagePack.Formatters.PerfBenchmarkDotNet.StringKeySerializerTargetFormatter(); - case 23: return new MessagePack.Formatters.QuestMessageBodyFormatter(); - case 24: return new MessagePack.Formatters.SharedData.ArrayOptimizeClassFormatter(); - case 25: return new MessagePack.Formatters.SharedData.BarClassFormatter(); - case 26: return new MessagePack.Formatters.SharedData.Callback1Formatter(); - case 27: return new MessagePack.Formatters.SharedData.Callback1_2Formatter(); - case 28: return new MessagePack.Formatters.SharedData.Callback2Formatter(); - case 29: return new MessagePack.Formatters.SharedData.Callback2_2Formatter(); - case 30: return new MessagePack.Formatters.SharedData.DefaultValueIntKeyClassWithExplicitConstructorFormatter(); - case 31: return new MessagePack.Formatters.SharedData.DefaultValueIntKeyClassWithoutExplicitConstructorFormatter(); - case 32: return new MessagePack.Formatters.SharedData.DefaultValueIntKeyStructWithExplicitConstructorFormatter(); - case 33: return new MessagePack.Formatters.SharedData.DefaultValueStringKeyClassWithExplicitConstructorFormatter(); - case 34: return new MessagePack.Formatters.SharedData.DefaultValueStringKeyClassWithoutExplicitConstructorFormatter(); - case 35: return new MessagePack.Formatters.SharedData.DefaultValueStringKeyStructWithExplicitConstructorFormatter(); - case 36: return new MessagePack.Formatters.SharedData.Empty1Formatter(); - case 37: return new MessagePack.Formatters.SharedData.Empty2Formatter(); - case 38: return new MessagePack.Formatters.SharedData.EmptyClassFormatter(); - case 39: return new MessagePack.Formatters.SharedData.EmptyStructFormatter(); - case 40: return new MessagePack.Formatters.SharedData.FirstSimpleDataFormatter(); - case 41: return new MessagePack.Formatters.SharedData.FooClassFormatter(); - case 42: return new MessagePack.Formatters.SharedData.HolderV0Formatter(); - case 43: return new MessagePack.Formatters.SharedData.HolderV1Formatter(); - case 44: return new MessagePack.Formatters.SharedData.HolderV2Formatter(); - case 45: return new MessagePack.Formatters.SharedData.MyClassFormatter(); - case 46: return new MessagePack.Formatters.SharedData.MySubUnion1Formatter(); - case 47: return new MessagePack.Formatters.SharedData.MySubUnion2Formatter(); - case 48: return new MessagePack.Formatters.SharedData.MySubUnion3Formatter(); - case 49: return new MessagePack.Formatters.SharedData.MySubUnion4Formatter(); - case 50: return new MessagePack.Formatters.SharedData.NestParent_NestContractFormatter(); - case 51: return new MessagePack.Formatters.SharedData.NonEmpty1Formatter(); - case 52: return new MessagePack.Formatters.SharedData.NonEmpty2Formatter(); - case 53: return new MessagePack.Formatters.SharedData.SimpleIntKeyDataFormatter(); - case 54: return new MessagePack.Formatters.SharedData.SimpleStringKeyDataFormatter(); - case 55: return new MessagePack.Formatters.SharedData.SimpleStructIntKeyDataFormatter(); - case 56: return new MessagePack.Formatters.SharedData.SimpleStructStringKeyDataFormatter(); - case 57: return new MessagePack.Formatters.SharedData.SubUnionType1Formatter(); - case 58: return new MessagePack.Formatters.SharedData.SubUnionType2Formatter(); - case 59: return new MessagePack.Formatters.SharedData.UnVersionBlockTestFormatter(); - case 60: return new MessagePack.Formatters.SharedData.Vector2Formatter(); - case 61: return new MessagePack.Formatters.SharedData.Vector3LikeFormatter(); - case 62: return new MessagePack.Formatters.SharedData.VectorLike2Formatter(); - case 63: return new MessagePack.Formatters.SharedData.Version0Formatter(); - case 64: return new MessagePack.Formatters.SharedData.Version1Formatter(); - case 65: return new MessagePack.Formatters.SharedData.Version2Formatter(); - case 66: return new MessagePack.Formatters.SharedData.VersionBlockTestFormatter(); - case 67: return new MessagePack.Formatters.SharedData.VersioningUnionFormatter(); - case 68: return new MessagePack.Formatters.SharedData.WithIndexerFormatter(); - case 69: return new MessagePack.Formatters.SimpleModelFormatter(); - case 70: return new MessagePack.Formatters.StampMessageBodyFormatter(); - case 71: return new MessagePack.Formatters.TextMessageBodyFormatter(); - default: return null; - } - } - } -} - -#pragma warning restore 168 -#pragma warning restore 414 -#pragma warning restore 618 -#pragma warning restore 612 - -#pragma warning restore SA1312 // Variable names should begin with lower-case letter -#pragma warning restore SA1649 // File name should match first type name - - -// -// THIS (.cs) FILE IS GENERATED BY MPC(MessagePack-CSharp). DO NOT CHANGE IT. -// - -#pragma warning disable 618 -#pragma warning disable 612 -#pragma warning disable 414 -#pragma warning disable 168 -#pragma warning disable CS1591 // document public APIs - -#pragma warning disable SA1403 // File may only contain a single namespace -#pragma warning disable SA1649 // File name should match first type name - -namespace MessagePack.Formatters -{ - - public sealed class GlobalMyEnumFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::GlobalMyEnum value, global::MessagePack.MessagePackSerializerOptions options) - { - writer.Write((global::System.Int32)value); - } - - public global::GlobalMyEnum Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - return (global::GlobalMyEnum)reader.ReadInt32(); - } - } -} - -#pragma warning restore 168 -#pragma warning restore 414 -#pragma warning restore 618 -#pragma warning restore 612 - -#pragma warning restore SA1403 // File may only contain a single namespace -#pragma warning restore SA1649 // File name should match first type name - -// -// THIS (.cs) FILE IS GENERATED BY MPC(MessagePack-CSharp). DO NOT CHANGE IT. -// - -#pragma warning disable 618 -#pragma warning disable 612 -#pragma warning disable 414 -#pragma warning disable 168 -#pragma warning disable CS1591 // document public APIs - -#pragma warning disable SA1403 // File may only contain a single namespace -#pragma warning disable SA1649 // File name should match first type name - -namespace MessagePack.Formatters.SharedData -{ - - public sealed class ByteEnumFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.ByteEnum value, global::MessagePack.MessagePackSerializerOptions options) - { - writer.Write((global::System.Byte)value); - } - - public global::SharedData.ByteEnum Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - return (global::SharedData.ByteEnum)reader.ReadByte(); - } - } -} - -#pragma warning restore 168 -#pragma warning restore 414 -#pragma warning restore 618 -#pragma warning restore 612 - -#pragma warning restore SA1403 // File may only contain a single namespace -#pragma warning restore SA1649 // File name should match first type name - - -// -// THIS (.cs) FILE IS GENERATED BY MPC(MessagePack-CSharp). DO NOT CHANGE IT. -// - -#pragma warning disable 618 -#pragma warning disable 612 -#pragma warning disable 414 -#pragma warning disable 168 -#pragma warning disable CS1591 // document public APIs - -#pragma warning disable SA1403 // File may only contain a single namespace -#pragma warning disable SA1649 // File name should match first type name - -namespace MessagePack.Formatters -{ - public sealed class IMessageBodyFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - private readonly global::System.Collections.Generic.Dictionary> typeToKeyAndJumpMap; - private readonly global::System.Collections.Generic.Dictionary keyToJumpMap; - - public IMessageBodyFormatter() - { - this.typeToKeyAndJumpMap = new global::System.Collections.Generic.Dictionary>(3, global::MessagePack.Internal.RuntimeTypeHandleEqualityComparer.Default) - { - { typeof(global::TextMessageBody).TypeHandle, new global::System.Collections.Generic.KeyValuePair(10, 0) }, - { typeof(global::StampMessageBody).TypeHandle, new global::System.Collections.Generic.KeyValuePair(14, 1) }, - { typeof(global::QuestMessageBody).TypeHandle, new global::System.Collections.Generic.KeyValuePair(25, 2) }, - }; - this.keyToJumpMap = new global::System.Collections.Generic.Dictionary(3) - { - { 10, 0 }, - { 14, 1 }, - { 25, 2 }, - }; - } - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::IMessageBody value, global::MessagePack.MessagePackSerializerOptions options) - { - global::System.Collections.Generic.KeyValuePair keyValuePair; - if (value != null && this.typeToKeyAndJumpMap.TryGetValue(value.GetType().TypeHandle, out keyValuePair)) - { - writer.WriteArrayHeader(2); - writer.WriteInt32(keyValuePair.Key); - switch (keyValuePair.Value) - { - case 0: - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(options.Resolver).Serialize(ref writer, (global::TextMessageBody)value, options); - break; - case 1: - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(options.Resolver).Serialize(ref writer, (global::StampMessageBody)value, options); - break; - case 2: - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(options.Resolver).Serialize(ref writer, (global::QuestMessageBody)value, options); - break; - default: - break; - } - - return; - } - - writer.WriteNil(); - } - - public global::IMessageBody Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - if (reader.ReadArrayHeader() != 2) - { - throw new global::System.InvalidOperationException("Invalid Union data was detected. Type:global::IMessageBody"); - } - - options.Security.DepthStep(ref reader); - var key = reader.ReadInt32(); - - if (!this.keyToJumpMap.TryGetValue(key, out key)) - { - key = -1; - } - - global::IMessageBody result = null; - switch (key) - { - case 0: - result = (global::IMessageBody)global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(options.Resolver).Deserialize(ref reader, options); - break; - case 1: - result = (global::IMessageBody)global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(options.Resolver).Deserialize(ref reader, options); - break; - case 2: - result = (global::IMessageBody)global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(options.Resolver).Deserialize(ref reader, options); - break; - default: - reader.Skip(); - break; - } - - reader.Depth--; - return result; - } - } - - -} - -#pragma warning restore 168 -#pragma warning restore 414 -#pragma warning restore 618 -#pragma warning restore 612 - -#pragma warning restore SA1403 // File may only contain a single namespace -#pragma warning restore SA1649 // File name should match first type name - -// -// THIS (.cs) FILE IS GENERATED BY MPC(MessagePack-CSharp). DO NOT CHANGE IT. -// - -#pragma warning disable 618 -#pragma warning disable 612 -#pragma warning disable 414 -#pragma warning disable 168 -#pragma warning disable CS1591 // document public APIs - -#pragma warning disable SA1403 // File may only contain a single namespace -#pragma warning disable SA1649 // File name should match first type name - -namespace MessagePack.Formatters.SharedData -{ - public sealed class IIVersioningUnionFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - private readonly global::System.Collections.Generic.Dictionary> typeToKeyAndJumpMap; - private readonly global::System.Collections.Generic.Dictionary keyToJumpMap; - - public IIVersioningUnionFormatter() - { - this.typeToKeyAndJumpMap = new global::System.Collections.Generic.Dictionary>(1, global::MessagePack.Internal.RuntimeTypeHandleEqualityComparer.Default) - { - { typeof(global::SharedData.MySubUnion1).TypeHandle, new global::System.Collections.Generic.KeyValuePair(0, 0) }, - }; - this.keyToJumpMap = new global::System.Collections.Generic.Dictionary(1) - { - { 0, 0 }, - }; - } - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.IIVersioningUnion value, global::MessagePack.MessagePackSerializerOptions options) - { - global::System.Collections.Generic.KeyValuePair keyValuePair; - if (value != null && this.typeToKeyAndJumpMap.TryGetValue(value.GetType().TypeHandle, out keyValuePair)) - { - writer.WriteArrayHeader(2); - writer.WriteInt32(keyValuePair.Key); - switch (keyValuePair.Value) - { - case 0: - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(options.Resolver).Serialize(ref writer, (global::SharedData.MySubUnion1)value, options); - break; - default: - break; - } - - return; - } - - writer.WriteNil(); - } - - public global::SharedData.IIVersioningUnion Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - if (reader.ReadArrayHeader() != 2) - { - throw new global::System.InvalidOperationException("Invalid Union data was detected. Type:global::SharedData.IIVersioningUnion"); - } - - options.Security.DepthStep(ref reader); - var key = reader.ReadInt32(); - - if (!this.keyToJumpMap.TryGetValue(key, out key)) - { - key = -1; - } - - global::SharedData.IIVersioningUnion result = null; - switch (key) - { - case 0: - result = (global::SharedData.IIVersioningUnion)global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(options.Resolver).Deserialize(ref reader, options); - break; - default: - reader.Skip(); - break; - } - - reader.Depth--; - return result; - } - } - - public sealed class IUnionCheckerFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - private readonly global::System.Collections.Generic.Dictionary> typeToKeyAndJumpMap; - private readonly global::System.Collections.Generic.Dictionary keyToJumpMap; - - public IUnionCheckerFormatter() - { - this.typeToKeyAndJumpMap = new global::System.Collections.Generic.Dictionary>(4, global::MessagePack.Internal.RuntimeTypeHandleEqualityComparer.Default) - { - { typeof(global::SharedData.MySubUnion1).TypeHandle, new global::System.Collections.Generic.KeyValuePair(0, 0) }, - { typeof(global::SharedData.MySubUnion2).TypeHandle, new global::System.Collections.Generic.KeyValuePair(1, 1) }, - { typeof(global::SharedData.MySubUnion3).TypeHandle, new global::System.Collections.Generic.KeyValuePair(2, 2) }, - { typeof(global::SharedData.MySubUnion4).TypeHandle, new global::System.Collections.Generic.KeyValuePair(3, 3) }, - }; - this.keyToJumpMap = new global::System.Collections.Generic.Dictionary(4) - { - { 0, 0 }, - { 1, 1 }, - { 2, 2 }, - { 3, 3 }, - }; - } - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.IUnionChecker value, global::MessagePack.MessagePackSerializerOptions options) - { - global::System.Collections.Generic.KeyValuePair keyValuePair; - if (value != null && this.typeToKeyAndJumpMap.TryGetValue(value.GetType().TypeHandle, out keyValuePair)) - { - writer.WriteArrayHeader(2); - writer.WriteInt32(keyValuePair.Key); - switch (keyValuePair.Value) - { - case 0: - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(options.Resolver).Serialize(ref writer, (global::SharedData.MySubUnion1)value, options); - break; - case 1: - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(options.Resolver).Serialize(ref writer, (global::SharedData.MySubUnion2)value, options); - break; - case 2: - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(options.Resolver).Serialize(ref writer, (global::SharedData.MySubUnion3)value, options); - break; - case 3: - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(options.Resolver).Serialize(ref writer, (global::SharedData.MySubUnion4)value, options); - break; - default: - break; - } - - return; - } - - writer.WriteNil(); - } - - public global::SharedData.IUnionChecker Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - if (reader.ReadArrayHeader() != 2) - { - throw new global::System.InvalidOperationException("Invalid Union data was detected. Type:global::SharedData.IUnionChecker"); - } - - options.Security.DepthStep(ref reader); - var key = reader.ReadInt32(); - - if (!this.keyToJumpMap.TryGetValue(key, out key)) - { - key = -1; - } - - global::SharedData.IUnionChecker result = null; - switch (key) - { - case 0: - result = (global::SharedData.IUnionChecker)global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(options.Resolver).Deserialize(ref reader, options); - break; - case 1: - result = (global::SharedData.IUnionChecker)global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(options.Resolver).Deserialize(ref reader, options); - break; - case 2: - result = (global::SharedData.IUnionChecker)global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(options.Resolver).Deserialize(ref reader, options); - break; - case 3: - result = (global::SharedData.IUnionChecker)global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(options.Resolver).Deserialize(ref reader, options); - break; - default: - reader.Skip(); - break; - } - - reader.Depth--; - return result; - } - } - - public sealed class IUnionChecker2Formatter : global::MessagePack.Formatters.IMessagePackFormatter - { - private readonly global::System.Collections.Generic.Dictionary> typeToKeyAndJumpMap; - private readonly global::System.Collections.Generic.Dictionary keyToJumpMap; - - public IUnionChecker2Formatter() - { - this.typeToKeyAndJumpMap = new global::System.Collections.Generic.Dictionary>(4, global::MessagePack.Internal.RuntimeTypeHandleEqualityComparer.Default) - { - { typeof(global::SharedData.MySubUnion2).TypeHandle, new global::System.Collections.Generic.KeyValuePair(31, 0) }, - { typeof(global::SharedData.MySubUnion3).TypeHandle, new global::System.Collections.Generic.KeyValuePair(42, 1) }, - { typeof(global::SharedData.MySubUnion4).TypeHandle, new global::System.Collections.Generic.KeyValuePair(63, 2) }, - { typeof(global::SharedData.MySubUnion1).TypeHandle, new global::System.Collections.Generic.KeyValuePair(120, 3) }, - }; - this.keyToJumpMap = new global::System.Collections.Generic.Dictionary(4) - { - { 31, 0 }, - { 42, 1 }, - { 63, 2 }, - { 120, 3 }, - }; - } - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.IUnionChecker2 value, global::MessagePack.MessagePackSerializerOptions options) - { - global::System.Collections.Generic.KeyValuePair keyValuePair; - if (value != null && this.typeToKeyAndJumpMap.TryGetValue(value.GetType().TypeHandle, out keyValuePair)) - { - writer.WriteArrayHeader(2); - writer.WriteInt32(keyValuePair.Key); - switch (keyValuePair.Value) - { - case 0: - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(options.Resolver).Serialize(ref writer, (global::SharedData.MySubUnion2)value, options); - break; - case 1: - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(options.Resolver).Serialize(ref writer, (global::SharedData.MySubUnion3)value, options); - break; - case 2: - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(options.Resolver).Serialize(ref writer, (global::SharedData.MySubUnion4)value, options); - break; - case 3: - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(options.Resolver).Serialize(ref writer, (global::SharedData.MySubUnion1)value, options); - break; - default: - break; - } - - return; - } - - writer.WriteNil(); - } - - public global::SharedData.IUnionChecker2 Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - if (reader.ReadArrayHeader() != 2) - { - throw new global::System.InvalidOperationException("Invalid Union data was detected. Type:global::SharedData.IUnionChecker2"); - } - - options.Security.DepthStep(ref reader); - var key = reader.ReadInt32(); - - if (!this.keyToJumpMap.TryGetValue(key, out key)) - { - key = -1; - } - - global::SharedData.IUnionChecker2 result = null; - switch (key) - { - case 0: - result = (global::SharedData.IUnionChecker2)global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(options.Resolver).Deserialize(ref reader, options); - break; - case 1: - result = (global::SharedData.IUnionChecker2)global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(options.Resolver).Deserialize(ref reader, options); - break; - case 2: - result = (global::SharedData.IUnionChecker2)global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(options.Resolver).Deserialize(ref reader, options); - break; - case 3: - result = (global::SharedData.IUnionChecker2)global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(options.Resolver).Deserialize(ref reader, options); - break; - default: - reader.Skip(); - break; - } - - reader.Depth--; - return result; - } - } - - public sealed class IUnionSampleFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - private readonly global::System.Collections.Generic.Dictionary> typeToKeyAndJumpMap; - private readonly global::System.Collections.Generic.Dictionary keyToJumpMap; - - public IUnionSampleFormatter() - { - this.typeToKeyAndJumpMap = new global::System.Collections.Generic.Dictionary>(2, global::MessagePack.Internal.RuntimeTypeHandleEqualityComparer.Default) - { - { typeof(global::SharedData.FooClass).TypeHandle, new global::System.Collections.Generic.KeyValuePair(0, 0) }, - { typeof(global::SharedData.BarClass).TypeHandle, new global::System.Collections.Generic.KeyValuePair(100, 1) }, - }; - this.keyToJumpMap = new global::System.Collections.Generic.Dictionary(2) - { - { 0, 0 }, - { 100, 1 }, - }; - } - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.IUnionSample value, global::MessagePack.MessagePackSerializerOptions options) - { - global::System.Collections.Generic.KeyValuePair keyValuePair; - if (value != null && this.typeToKeyAndJumpMap.TryGetValue(value.GetType().TypeHandle, out keyValuePair)) - { - writer.WriteArrayHeader(2); - writer.WriteInt32(keyValuePair.Key); - switch (keyValuePair.Value) - { - case 0: - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(options.Resolver).Serialize(ref writer, (global::SharedData.FooClass)value, options); - break; - case 1: - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(options.Resolver).Serialize(ref writer, (global::SharedData.BarClass)value, options); - break; - default: - break; - } - - return; - } - - writer.WriteNil(); - } - - public global::SharedData.IUnionSample Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - if (reader.ReadArrayHeader() != 2) - { - throw new global::System.InvalidOperationException("Invalid Union data was detected. Type:global::SharedData.IUnionSample"); - } - - options.Security.DepthStep(ref reader); - var key = reader.ReadInt32(); - - if (!this.keyToJumpMap.TryGetValue(key, out key)) - { - key = -1; - } - - global::SharedData.IUnionSample result = null; - switch (key) - { - case 0: - result = (global::SharedData.IUnionSample)global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(options.Resolver).Deserialize(ref reader, options); - break; - case 1: - result = (global::SharedData.IUnionSample)global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(options.Resolver).Deserialize(ref reader, options); - break; - default: - reader.Skip(); - break; - } - - reader.Depth--; - return result; - } - } - - public sealed class RootUnionTypeFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - private readonly global::System.Collections.Generic.Dictionary> typeToKeyAndJumpMap; - private readonly global::System.Collections.Generic.Dictionary keyToJumpMap; - - public RootUnionTypeFormatter() - { - this.typeToKeyAndJumpMap = new global::System.Collections.Generic.Dictionary>(2, global::MessagePack.Internal.RuntimeTypeHandleEqualityComparer.Default) - { - { typeof(global::SharedData.SubUnionType1).TypeHandle, new global::System.Collections.Generic.KeyValuePair(0, 0) }, - { typeof(global::SharedData.SubUnionType2).TypeHandle, new global::System.Collections.Generic.KeyValuePair(1, 1) }, - }; - this.keyToJumpMap = new global::System.Collections.Generic.Dictionary(2) - { - { 0, 0 }, - { 1, 1 }, - }; - } - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.RootUnionType value, global::MessagePack.MessagePackSerializerOptions options) - { - global::System.Collections.Generic.KeyValuePair keyValuePair; - if (value != null && this.typeToKeyAndJumpMap.TryGetValue(value.GetType().TypeHandle, out keyValuePair)) - { - writer.WriteArrayHeader(2); - writer.WriteInt32(keyValuePair.Key); - switch (keyValuePair.Value) - { - case 0: - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(options.Resolver).Serialize(ref writer, (global::SharedData.SubUnionType1)value, options); - break; - case 1: - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(options.Resolver).Serialize(ref writer, (global::SharedData.SubUnionType2)value, options); - break; - default: - break; - } - - return; - } - - writer.WriteNil(); - } - - public global::SharedData.RootUnionType Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - if (reader.ReadArrayHeader() != 2) - { - throw new global::System.InvalidOperationException("Invalid Union data was detected. Type:global::SharedData.RootUnionType"); - } - - options.Security.DepthStep(ref reader); - var key = reader.ReadInt32(); - - if (!this.keyToJumpMap.TryGetValue(key, out key)) - { - key = -1; - } - - global::SharedData.RootUnionType result = null; - switch (key) - { - case 0: - result = (global::SharedData.RootUnionType)global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(options.Resolver).Deserialize(ref reader, options); - break; - case 1: - result = (global::SharedData.RootUnionType)global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(options.Resolver).Deserialize(ref reader, options); - break; - default: - reader.Skip(); - break; - } - - reader.Depth--; - return result; - } - } - - -} - -#pragma warning restore 168 -#pragma warning restore 414 -#pragma warning restore 618 -#pragma warning restore 612 - -#pragma warning restore SA1403 // File may only contain a single namespace -#pragma warning restore SA1649 // File name should match first type name - - -// -// THIS (.cs) FILE IS GENERATED BY MPC(MessagePack-CSharp). DO NOT CHANGE IT. -// - -#pragma warning disable 618 -#pragma warning disable 612 -#pragma warning disable 414 -#pragma warning disable 168 -#pragma warning disable CS1591 // document public APIs - -#pragma warning disable SA1129 // Do not use default value type constructor -#pragma warning disable SA1309 // Field names should not begin with underscore -#pragma warning disable SA1312 // Variable names should begin with lower-case letter -#pragma warning disable SA1403 // File may only contain a single namespace -#pragma warning disable SA1649 // File name should match first type name - -namespace MessagePack.Formatters.Abcdefg.Efcdigjl.Ateatatea.Hgfagfafgad -{ - public sealed class TnonodsfarnoiuAtatqagaFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::Abcdefg.Efcdigjl.Ateatatea.Hgfagfafgad.TnonodsfarnoiuAtatqaga value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - writer.WriteArrayHeader(1); - writer.Write(value.MyProperty); - } - - public global::Abcdefg.Efcdigjl.Ateatatea.Hgfagfafgad.TnonodsfarnoiuAtatqaga Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadArrayHeader(); - var ____result = new global::Abcdefg.Efcdigjl.Ateatatea.Hgfagfafgad.TnonodsfarnoiuAtatqaga(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - ____result.MyProperty = reader.ReadInt32(); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - -} - -#pragma warning restore 168 -#pragma warning restore 414 -#pragma warning restore 618 -#pragma warning restore 612 - -#pragma warning restore SA1129 // Do not use default value type constructor -#pragma warning restore SA1309 // Field names should not begin with underscore -#pragma warning restore SA1312 // Variable names should begin with lower-case letter -#pragma warning restore SA1403 // File may only contain a single namespace -#pragma warning restore SA1649 // File name should match first type name - -// -// THIS (.cs) FILE IS GENERATED BY MPC(MessagePack-CSharp). DO NOT CHANGE IT. -// - -#pragma warning disable 618 -#pragma warning disable 612 -#pragma warning disable 414 -#pragma warning disable 168 -#pragma warning disable CS1591 // document public APIs - -#pragma warning disable SA1129 // Do not use default value type constructor -#pragma warning disable SA1309 // Field names should not begin with underscore -#pragma warning disable SA1312 // Variable names should begin with lower-case letter -#pragma warning disable SA1403 // File may only contain a single namespace -#pragma warning disable SA1649 // File name should match first type name - -namespace MessagePack.Formatters -{ - public sealed class ArrayTestTestFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::ArrayTestTest value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - writer.WriteArrayHeader(7); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.MyProperty0, options); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.MyProperty1, options); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.MyProperty2, options); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.MyProperty3, options); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.MyProperty4, options); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.MyProperty5, options); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.MyProperty6, options); - } - - public global::ArrayTestTest Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - var length = reader.ReadArrayHeader(); - var ____result = new global::ArrayTestTest(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - ____result.MyProperty0 = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - case 1: - ____result.MyProperty1 = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - case 2: - ____result.MyProperty2 = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - case 3: - ____result.MyProperty3 = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - case 4: - ____result.MyProperty4 = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - case 5: - ____result.MyProperty5 = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - case 6: - ____result.MyProperty6 = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class GlobalManFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::GlobalMan value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - writer.WriteArrayHeader(1); - writer.Write(value.MyProperty); - } - - public global::GlobalMan Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadArrayHeader(); - var ____result = new global::GlobalMan(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - ____result.MyProperty = reader.ReadInt32(); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class MessageFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::Message value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - writer.WriteArrayHeader(4); - writer.Write(value.UserId); - writer.Write(value.RoomId); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.PostTime, options); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.Body, options); - } - - public global::Message Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - var length = reader.ReadArrayHeader(); - var ____result = new global::Message(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - ____result.UserId = reader.ReadInt32(); - break; - case 1: - ____result.RoomId = reader.ReadInt32(); - break; - case 2: - ____result.PostTime = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - case 3: - ____result.Body = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class MessagePackFormatterFieldUserFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - private readonly global::MessagePack.Formatters.NativeDateTimeFormatter __TimestampCustomFormatter__ = new global::MessagePack.Formatters.NativeDateTimeFormatter(); - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::MessagePackFormatterFieldUser value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - writer.WriteArrayHeader(1); - this.__TimestampCustomFormatter__.Serialize(ref writer, value.Timestamp, options); - } - - public global::MessagePackFormatterFieldUser Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadArrayHeader(); - var ____result = new global::MessagePackFormatterFieldUser(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - ____result.Timestamp = this.__TimestampCustomFormatter__.Deserialize(ref reader, options); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class QuestMessageBodyFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::QuestMessageBody value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - writer.WriteArrayHeader(2); - writer.Write(value.QuestId); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.Text, options); - } - - public global::QuestMessageBody Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - var length = reader.ReadArrayHeader(); - var ____result = new global::QuestMessageBody(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - ____result.QuestId = reader.ReadInt32(); - break; - case 1: - ____result.Text = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class StampMessageBodyFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::StampMessageBody value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - writer.WriteArrayHeader(1); - writer.Write(value.StampId); - } - - public global::StampMessageBody Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadArrayHeader(); - var ____result = new global::StampMessageBody(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - ____result.StampId = reader.ReadInt32(); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class TextMessageBodyFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::TextMessageBody value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - writer.WriteArrayHeader(1); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.Text, options); - } - - public global::TextMessageBody Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - var length = reader.ReadArrayHeader(); - var ____result = new global::TextMessageBody(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - ____result.Text = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - -} - -#pragma warning restore 168 -#pragma warning restore 414 -#pragma warning restore 618 -#pragma warning restore 612 - -#pragma warning restore SA1129 // Do not use default value type constructor -#pragma warning restore SA1309 // Field names should not begin with underscore -#pragma warning restore SA1312 // Variable names should begin with lower-case letter -#pragma warning restore SA1403 // File may only contain a single namespace -#pragma warning restore SA1649 // File name should match first type name - -// -// THIS (.cs) FILE IS GENERATED BY MPC(MessagePack-CSharp). DO NOT CHANGE IT. -// - -#pragma warning disable 618 -#pragma warning disable 612 -#pragma warning disable 414 -#pragma warning disable 168 -#pragma warning disable CS1591 // document public APIs - -#pragma warning disable SA1129 // Do not use default value type constructor -#pragma warning disable SA1309 // Field names should not begin with underscore -#pragma warning disable SA1312 // Variable names should begin with lower-case letter -#pragma warning disable SA1403 // File may only contain a single namespace -#pragma warning disable SA1649 // File name should match first type name - -namespace MessagePack.Formatters -{ - public sealed class ComplexModelFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - // AdditionalProperty - private static global::System.ReadOnlySpan GetSpan_AdditionalProperty() => new byte[1 + 18] { 178, 65, 100, 100, 105, 116, 105, 111, 110, 97, 108, 80, 114, 111, 112, 101, 114, 116, 121 }; - // CreatedOn - private static global::System.ReadOnlySpan GetSpan_CreatedOn() => new byte[1 + 9] { 169, 67, 114, 101, 97, 116, 101, 100, 79, 110 }; - // Id - private static global::System.ReadOnlySpan GetSpan_Id() => new byte[1 + 2] { 162, 73, 100 }; - // Name - private static global::System.ReadOnlySpan GetSpan_Name() => new byte[1 + 4] { 164, 78, 97, 109, 101 }; - // UpdatedOn - private static global::System.ReadOnlySpan GetSpan_UpdatedOn() => new byte[1 + 9] { 169, 85, 112, 100, 97, 116, 101, 100, 79, 110 }; - // SimpleModels - private static global::System.ReadOnlySpan GetSpan_SimpleModels() => new byte[1 + 12] { 172, 83, 105, 109, 112, 108, 101, 77, 111, 100, 101, 108, 115 }; - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::ComplexModel value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value is null) - { - writer.WriteNil(); - return; - } - - var formatterResolver = options.Resolver; - writer.WriteMapHeader(6); - writer.WriteRaw(GetSpan_AdditionalProperty()); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify>(formatterResolver).Serialize(ref writer, value.AdditionalProperty, options); - writer.WriteRaw(GetSpan_CreatedOn()); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.CreatedOn, options); - writer.WriteRaw(GetSpan_Id()); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.Id, options); - writer.WriteRaw(GetSpan_Name()); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.Name, options); - writer.WriteRaw(GetSpan_UpdatedOn()); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.UpdatedOn, options); - writer.WriteRaw(GetSpan_SimpleModels()); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify>(formatterResolver).Serialize(ref writer, value.SimpleModels, options); - } - - public global::ComplexModel Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var formatterResolver = options.Resolver; - var length = reader.ReadMapHeader(); - var ____result = new global::ComplexModel(); - - for (int i = 0; i < length; i++) - { - var stringKey = global::MessagePack.Internal.CodeGenHelpers.ReadStringSpan(ref reader); - switch (stringKey.Length) - { - default: - FAIL: - reader.Skip(); - continue; - case 18: - if (!global::System.MemoryExtensions.SequenceEqual(stringKey, GetSpan_AdditionalProperty().Slice(1))) { goto FAIL; } - - reader.Skip(); - continue; - case 9: - switch (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey)) - { - default: goto FAIL; - case 5720808977192022595UL: - if (stringKey[0] != 110) { goto FAIL; } - - ____result.CreatedOn = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - continue; - - case 5720808977191956565UL: - if (stringKey[0] != 110) { goto FAIL; } - - ____result.UpdatedOn = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - continue; - - } - case 2: - if (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey) != 25673UL) { goto FAIL; } - - ____result.Id = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - continue; - case 4: - if (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey) != 1701667150UL) { goto FAIL; } - - ____result.Name = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - continue; - case 12: - if (!global::System.MemoryExtensions.SequenceEqual(stringKey, GetSpan_SimpleModels().Slice(1))) { goto FAIL; } - - reader.Skip(); - continue; - - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class SimpleModelFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - // Id - private static global::System.ReadOnlySpan GetSpan_Id() => new byte[1 + 2] { 162, 73, 100 }; - // Name - private static global::System.ReadOnlySpan GetSpan_Name() => new byte[1 + 4] { 164, 78, 97, 109, 101 }; - // CreatedOn - private static global::System.ReadOnlySpan GetSpan_CreatedOn() => new byte[1 + 9] { 169, 67, 114, 101, 97, 116, 101, 100, 79, 110 }; - // Precision - private static global::System.ReadOnlySpan GetSpan_Precision() => new byte[1 + 9] { 169, 80, 114, 101, 99, 105, 115, 105, 111, 110 }; - // Money - private static global::System.ReadOnlySpan GetSpan_Money() => new byte[1 + 5] { 165, 77, 111, 110, 101, 121 }; - // Amount - private static global::System.ReadOnlySpan GetSpan_Amount() => new byte[1 + 6] { 166, 65, 109, 111, 117, 110, 116 }; - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SimpleModel value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value is null) - { - writer.WriteNil(); - return; - } - - var formatterResolver = options.Resolver; - writer.WriteMapHeader(6); - writer.WriteRaw(GetSpan_Id()); - writer.Write(value.Id); - writer.WriteRaw(GetSpan_Name()); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.Name, options); - writer.WriteRaw(GetSpan_CreatedOn()); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.CreatedOn, options); - writer.WriteRaw(GetSpan_Precision()); - writer.Write(value.Precision); - writer.WriteRaw(GetSpan_Money()); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.Money, options); - writer.WriteRaw(GetSpan_Amount()); - writer.Write(value.Amount); - } - - public global::SimpleModel Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var formatterResolver = options.Resolver; - var length = reader.ReadMapHeader(); - var ____result = new global::SimpleModel(); - - for (int i = 0; i < length; i++) - { - var stringKey = global::MessagePack.Internal.CodeGenHelpers.ReadStringSpan(ref reader); - switch (stringKey.Length) - { - default: - FAIL: - reader.Skip(); - continue; - case 2: - if (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey) != 25673UL) { goto FAIL; } - - ____result.Id = reader.ReadInt32(); - continue; - case 4: - if (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey) != 1701667150UL) { goto FAIL; } - - ____result.Name = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - continue; - case 9: - switch (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey)) - { - default: goto FAIL; - case 5720808977192022595UL: - if (stringKey[0] != 110) { goto FAIL; } - - ____result.CreatedOn = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - continue; - - case 8028074707240972880UL: - if (stringKey[0] != 110) { goto FAIL; } - - ____result.Precision = reader.ReadInt32(); - continue; - - } - case 5: - if (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey) != 521392779085UL) { goto FAIL; } - - ____result.Money = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - continue; - case 6: - if (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey) != 128017765461313UL) { goto FAIL; } - - reader.Skip(); - continue; - - } - } - - reader.Depth--; - return ____result; - } - } - -} - -#pragma warning restore 168 -#pragma warning restore 414 -#pragma warning restore 618 -#pragma warning restore 612 - -#pragma warning restore SA1129 // Do not use default value type constructor -#pragma warning restore SA1309 // Field names should not begin with underscore -#pragma warning restore SA1312 // Variable names should begin with lower-case letter -#pragma warning restore SA1403 // File may only contain a single namespace -#pragma warning restore SA1649 // File name should match first type name - -// -// THIS (.cs) FILE IS GENERATED BY MPC(MessagePack-CSharp). DO NOT CHANGE IT. -// - -#pragma warning disable 618 -#pragma warning disable 612 -#pragma warning disable 414 -#pragma warning disable 168 -#pragma warning disable CS1591 // document public APIs - -#pragma warning disable SA1129 // Do not use default value type constructor -#pragma warning disable SA1309 // Field names should not begin with underscore -#pragma warning disable SA1312 // Variable names should begin with lower-case letter -#pragma warning disable SA1403 // File may only contain a single namespace -#pragma warning disable SA1649 // File name should match first type name - -namespace MessagePack.Formatters.PerfBenchmarkDotNet -{ - public sealed class StringKeySerializerTargetFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - // MyProperty1 - private static global::System.ReadOnlySpan GetSpan_MyProperty1() => new byte[1 + 11] { 171, 77, 121, 80, 114, 111, 112, 101, 114, 116, 121, 49 }; - // MyProperty2 - private static global::System.ReadOnlySpan GetSpan_MyProperty2() => new byte[1 + 11] { 171, 77, 121, 80, 114, 111, 112, 101, 114, 116, 121, 50 }; - // MyProperty3 - private static global::System.ReadOnlySpan GetSpan_MyProperty3() => new byte[1 + 11] { 171, 77, 121, 80, 114, 111, 112, 101, 114, 116, 121, 51 }; - // MyProperty4 - private static global::System.ReadOnlySpan GetSpan_MyProperty4() => new byte[1 + 11] { 171, 77, 121, 80, 114, 111, 112, 101, 114, 116, 121, 52 }; - // MyProperty5 - private static global::System.ReadOnlySpan GetSpan_MyProperty5() => new byte[1 + 11] { 171, 77, 121, 80, 114, 111, 112, 101, 114, 116, 121, 53 }; - // MyProperty6 - private static global::System.ReadOnlySpan GetSpan_MyProperty6() => new byte[1 + 11] { 171, 77, 121, 80, 114, 111, 112, 101, 114, 116, 121, 54 }; - // MyProperty7 - private static global::System.ReadOnlySpan GetSpan_MyProperty7() => new byte[1 + 11] { 171, 77, 121, 80, 114, 111, 112, 101, 114, 116, 121, 55 }; - // MyProperty8 - private static global::System.ReadOnlySpan GetSpan_MyProperty8() => new byte[1 + 11] { 171, 77, 121, 80, 114, 111, 112, 101, 114, 116, 121, 56 }; - // MyProperty9 - private static global::System.ReadOnlySpan GetSpan_MyProperty9() => new byte[1 + 11] { 171, 77, 121, 80, 114, 111, 112, 101, 114, 116, 121, 57 }; - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::PerfBenchmarkDotNet.StringKeySerializerTarget value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value is null) - { - writer.WriteNil(); - return; - } - - writer.WriteMapHeader(9); - writer.WriteRaw(GetSpan_MyProperty1()); - writer.Write(value.MyProperty1); - writer.WriteRaw(GetSpan_MyProperty2()); - writer.Write(value.MyProperty2); - writer.WriteRaw(GetSpan_MyProperty3()); - writer.Write(value.MyProperty3); - writer.WriteRaw(GetSpan_MyProperty4()); - writer.Write(value.MyProperty4); - writer.WriteRaw(GetSpan_MyProperty5()); - writer.Write(value.MyProperty5); - writer.WriteRaw(GetSpan_MyProperty6()); - writer.Write(value.MyProperty6); - writer.WriteRaw(GetSpan_MyProperty7()); - writer.Write(value.MyProperty7); - writer.WriteRaw(GetSpan_MyProperty8()); - writer.Write(value.MyProperty8); - writer.WriteRaw(GetSpan_MyProperty9()); - writer.Write(value.MyProperty9); - } - - public global::PerfBenchmarkDotNet.StringKeySerializerTarget Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadMapHeader(); - var ____result = new global::PerfBenchmarkDotNet.StringKeySerializerTarget(); - - for (int i = 0; i < length; i++) - { - var stringKey = global::MessagePack.Internal.CodeGenHelpers.ReadStringSpan(ref reader); - switch (stringKey.Length) - { - default: - FAIL: - reader.Skip(); - continue; - case 11: - switch (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey)) - { - default: goto FAIL; - case 8243118316933118285UL: - switch (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey)) - { - default: goto FAIL; - case 3242356UL: - ____result.MyProperty1 = reader.ReadInt32(); - continue; - case 3307892UL: - ____result.MyProperty2 = reader.ReadInt32(); - continue; - case 3373428UL: - ____result.MyProperty3 = reader.ReadInt32(); - continue; - case 3438964UL: - ____result.MyProperty4 = reader.ReadInt32(); - continue; - case 3504500UL: - ____result.MyProperty5 = reader.ReadInt32(); - continue; - case 3570036UL: - ____result.MyProperty6 = reader.ReadInt32(); - continue; - case 3635572UL: - ____result.MyProperty7 = reader.ReadInt32(); - continue; - case 3701108UL: - ____result.MyProperty8 = reader.ReadInt32(); - continue; - case 3766644UL: - ____result.MyProperty9 = reader.ReadInt32(); - continue; - } - - } - - } - } - - reader.Depth--; - return ____result; - } - } - -} - -#pragma warning restore 168 -#pragma warning restore 414 -#pragma warning restore 618 -#pragma warning restore 612 - -#pragma warning restore SA1129 // Do not use default value type constructor -#pragma warning restore SA1309 // Field names should not begin with underscore -#pragma warning restore SA1312 // Variable names should begin with lower-case letter -#pragma warning restore SA1403 // File may only contain a single namespace -#pragma warning restore SA1649 // File name should match first type name - -// -// THIS (.cs) FILE IS GENERATED BY MPC(MessagePack-CSharp). DO NOT CHANGE IT. -// - -#pragma warning disable 618 -#pragma warning disable 612 -#pragma warning disable 414 -#pragma warning disable 168 -#pragma warning disable CS1591 // document public APIs - -#pragma warning disable SA1129 // Do not use default value type constructor -#pragma warning disable SA1309 // Field names should not begin with underscore -#pragma warning disable SA1312 // Variable names should begin with lower-case letter -#pragma warning disable SA1403 // File may only contain a single namespace -#pragma warning disable SA1649 // File name should match first type name - -namespace MessagePack.Formatters.SharedData -{ - public sealed class ArrayOptimizeClassFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.ArrayOptimizeClass value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - writer.WriteArrayHeader(16); - writer.Write(value.MyProperty0); - writer.Write(value.MyProperty1); - writer.Write(value.MyProperty2); - writer.Write(value.MyProperty3); - writer.Write(value.MyProperty4); - writer.Write(value.MyProperty5); - writer.Write(value.MyProperty6); - writer.Write(value.MyProperty7); - writer.Write(value.MyProperty8); - writer.Write(value.MyProvperty9); - writer.Write(value.MyProperty10); - writer.Write(value.MyProperty11); - writer.Write(value.MyPropverty12); - writer.Write(value.MyPropevrty13); - writer.Write(value.MyProperty14); - writer.Write(value.MyProperty15); - } - - public global::SharedData.ArrayOptimizeClass Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadArrayHeader(); - var ____result = new global::SharedData.ArrayOptimizeClass(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - ____result.MyProperty0 = reader.ReadInt32(); - break; - case 1: - ____result.MyProperty1 = reader.ReadInt32(); - break; - case 2: - ____result.MyProperty2 = reader.ReadInt32(); - break; - case 3: - ____result.MyProperty3 = reader.ReadInt32(); - break; - case 4: - ____result.MyProperty4 = reader.ReadInt32(); - break; - case 5: - ____result.MyProperty5 = reader.ReadInt32(); - break; - case 6: - ____result.MyProperty6 = reader.ReadInt32(); - break; - case 7: - ____result.MyProperty7 = reader.ReadInt32(); - break; - case 8: - ____result.MyProperty8 = reader.ReadInt32(); - break; - case 9: - ____result.MyProvperty9 = reader.ReadInt32(); - break; - case 10: - ____result.MyProperty10 = reader.ReadInt32(); - break; - case 11: - ____result.MyProperty11 = reader.ReadInt32(); - break; - case 12: - ____result.MyPropverty12 = reader.ReadInt32(); - break; - case 13: - ____result.MyPropevrty13 = reader.ReadInt32(); - break; - case 14: - ____result.MyProperty14 = reader.ReadInt32(); - break; - case 15: - ____result.MyProperty15 = reader.ReadInt32(); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class BarClassFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.BarClass value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - writer.WriteArrayHeader(1); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.OPQ, options); - } - - public global::SharedData.BarClass Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - var length = reader.ReadArrayHeader(); - var ____result = new global::SharedData.BarClass(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - ____result.OPQ = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class Callback1Formatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.Callback1 value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - value.OnBeforeSerialize(); - writer.WriteArrayHeader(1); - writer.Write(value.X); - } - - public global::SharedData.Callback1 Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadArrayHeader(); - var __X__ = default(int); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - __X__ = reader.ReadInt32(); - break; - default: - reader.Skip(); - break; - } - } - - var ____result = new global::SharedData.Callback1(__X__); - ____result.OnAfterDeserialize(); - reader.Depth--; - return ____result; - } - } - - public sealed class Callback1_2Formatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.Callback1_2 value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - ((global::MessagePack.IMessagePackSerializationCallbackReceiver)value).OnBeforeSerialize(); - writer.WriteArrayHeader(1); - writer.Write(value.X); - } - - public global::SharedData.Callback1_2 Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadArrayHeader(); - var __X__ = default(int); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - __X__ = reader.ReadInt32(); - break; - default: - reader.Skip(); - break; - } - } - - var ____result = new global::SharedData.Callback1_2(__X__); - ((global::MessagePack.IMessagePackSerializationCallbackReceiver)____result).OnAfterDeserialize(); - reader.Depth--; - return ____result; - } - } - - public sealed class DefaultValueIntKeyClassWithExplicitConstructorFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.DefaultValueIntKeyClassWithExplicitConstructor value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - writer.WriteArrayHeader(4); - writer.Write(value.Prop1); - writer.Write(value.Prop2); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.Prop3, options); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.Prop4, options); - } - - public global::SharedData.DefaultValueIntKeyClassWithExplicitConstructor Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - var length = reader.ReadArrayHeader(); - var __Prop1__ = default(int); - var __Prop2__ = default(int); - var __Prop3__ = default(string); - var __Prop4__ = default(string); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - __Prop1__ = reader.ReadInt32(); - break; - case 1: - __Prop2__ = reader.ReadInt32(); - break; - case 2: - __Prop3__ = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - case 3: - __Prop4__ = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - default: - reader.Skip(); - break; - } - } - - var ____result = new global::SharedData.DefaultValueIntKeyClassWithExplicitConstructor(__Prop1__); - if (length <= 1) - { - goto MEMBER_ASSIGNMENT_END; - } - - ____result.Prop2 = __Prop2__; - if (length <= 2) - { - goto MEMBER_ASSIGNMENT_END; - } - - ____result.Prop3 = __Prop3__; - if (length <= 3) - { - goto MEMBER_ASSIGNMENT_END; - } - - ____result.Prop4 = __Prop4__; - - MEMBER_ASSIGNMENT_END: - reader.Depth--; - return ____result; - } - } - - public sealed class DefaultValueIntKeyClassWithoutExplicitConstructorFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.DefaultValueIntKeyClassWithoutExplicitConstructor value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - writer.WriteArrayHeader(2); - writer.Write(value.Prop1); - writer.Write(value.Prop2); - } - - public global::SharedData.DefaultValueIntKeyClassWithoutExplicitConstructor Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadArrayHeader(); - var ____result = new global::SharedData.DefaultValueIntKeyClassWithoutExplicitConstructor(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - ____result.Prop1 = reader.ReadInt32(); - break; - case 1: - ____result.Prop2 = reader.ReadInt32(); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class DefaultValueIntKeyStructWithExplicitConstructorFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.DefaultValueIntKeyStructWithExplicitConstructor value, global::MessagePack.MessagePackSerializerOptions options) - { - writer.WriteArrayHeader(2); - writer.Write(value.Prop1); - writer.Write(value.Prop2); - } - - public global::SharedData.DefaultValueIntKeyStructWithExplicitConstructor Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - throw new global::System.InvalidOperationException("typecode is null, struct not supported"); - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadArrayHeader(); - var __Prop1__ = default(int); - var __Prop2__ = default(int); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - __Prop1__ = reader.ReadInt32(); - break; - case 1: - __Prop2__ = reader.ReadInt32(); - break; - default: - reader.Skip(); - break; - } - } - - var ____result = new global::SharedData.DefaultValueIntKeyStructWithExplicitConstructor(__Prop1__); - if (length <= 1) - { - goto MEMBER_ASSIGNMENT_END; - } - - ____result.Prop2 = __Prop2__; - - MEMBER_ASSIGNMENT_END: - reader.Depth--; - return ____result; - } - } - - public sealed class DynamicArgumentTupleFormatter : global::MessagePack.Formatters.IMessagePackFormatter> - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.DynamicArgumentTuple value, global::MessagePack.MessagePackSerializerOptions options) - { - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - writer.WriteArrayHeader(9); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.Item1, options); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.Item2, options); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.Item3, options); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.Item4, options); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.Item5, options); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.Item6, options); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.Item7, options); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.Item8, options); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.Item9, options); - } - - public global::SharedData.DynamicArgumentTuple Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - throw new global::System.InvalidOperationException("typecode is null, struct not supported"); - } - - options.Security.DepthStep(ref reader); - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - var length = reader.ReadArrayHeader(); - var __Item1__ = default(T1); - var __Item2__ = default(T2); - var __Item3__ = default(T3); - var __Item4__ = default(T4); - var __Item5__ = default(T5); - var __Item6__ = default(T6); - var __Item7__ = default(T7); - var __Item8__ = default(T8); - var __Item9__ = default(T9); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - __Item1__ = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - case 1: - __Item2__ = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - case 2: - __Item3__ = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - case 3: - __Item4__ = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - case 4: - __Item5__ = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - case 5: - __Item6__ = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - case 6: - __Item7__ = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - case 7: - __Item8__ = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - case 8: - __Item9__ = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - default: - reader.Skip(); - break; - } - } - - var ____result = new global::SharedData.DynamicArgumentTuple(__Item1__, __Item2__, __Item3__, __Item4__, __Item5__, __Item6__, __Item7__, __Item8__, __Item9__); - reader.Depth--; - return ____result; - } - } - - public sealed class Empty1Formatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.Empty1 value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - writer.WriteArrayHeader(0); - } - - public global::SharedData.Empty1 Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - reader.Skip(); - return new global::SharedData.Empty1(); - } - } - - public sealed class EmptyClassFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.EmptyClass value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - writer.WriteArrayHeader(0); - } - - public global::SharedData.EmptyClass Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - reader.Skip(); - return new global::SharedData.EmptyClass(); - } - } - - public sealed class EmptyStructFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.EmptyStruct value, global::MessagePack.MessagePackSerializerOptions options) - { - writer.WriteArrayHeader(0); - } - - public global::SharedData.EmptyStruct Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - throw new global::System.InvalidOperationException("typecode is null, struct not supported"); - } - - reader.Skip(); - return new global::SharedData.EmptyStruct(); - } - } - - public sealed class FirstSimpleDataFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.FirstSimpleData value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - writer.WriteArrayHeader(3); - writer.Write(value.Prop1); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.Prop2, options); - writer.Write(value.Prop3); - } - - public global::SharedData.FirstSimpleData Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - var length = reader.ReadArrayHeader(); - var ____result = new global::SharedData.FirstSimpleData(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - ____result.Prop1 = reader.ReadInt32(); - break; - case 1: - ____result.Prop2 = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - case 2: - ____result.Prop3 = reader.ReadInt32(); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class FooClassFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.FooClass value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - writer.WriteArrayHeader(1); - writer.Write(value.XYZ); - } - - public global::SharedData.FooClass Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadArrayHeader(); - var ____result = new global::SharedData.FooClass(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - ____result.XYZ = reader.ReadInt32(); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class GenericClassFormatter : global::MessagePack.Formatters.IMessagePackFormatter> - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.GenericClass value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - writer.WriteArrayHeader(2); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.MyProperty0, options); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.MyProperty1, options); - } - - public global::SharedData.GenericClass Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - var length = reader.ReadArrayHeader(); - var ____result = new global::SharedData.GenericClass(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - ____result.MyProperty0 = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - case 1: - ____result.MyProperty1 = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class GenericConstrainedClassIntKeyFormatter : global::MessagePack.Formatters.IMessagePackFormatter> - where T1 : class - where T2 : class, global::System.Collections.Generic.IEqualityComparer - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.GenericConstrainedClassIntKey value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - writer.WriteArrayHeader(2); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.MyProperty0, options); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.Comparer, options); - } - - public global::SharedData.GenericConstrainedClassIntKey Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - var length = reader.ReadArrayHeader(); - var ____result = new global::SharedData.GenericConstrainedClassIntKey(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - ____result.MyProperty0 = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - case 1: - ____result.Comparer = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class GenericConstrainedStructIntKeyFormatter : global::MessagePack.Formatters.IMessagePackFormatter> - where T1 : unmanaged - where T2 : unmanaged, global::System.Collections.Generic.IEqualityComparer - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.GenericConstrainedStructIntKey value, global::MessagePack.MessagePackSerializerOptions options) - { - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - writer.WriteArrayHeader(2); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.MyProperty0, options); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.Comparer, options); - } - - public global::SharedData.GenericConstrainedStructIntKey Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - throw new global::System.InvalidOperationException("typecode is null, struct not supported"); - } - - options.Security.DepthStep(ref reader); - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - var length = reader.ReadArrayHeader(); - var ____result = new global::SharedData.GenericConstrainedStructIntKey(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - ____result.MyProperty0 = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - case 1: - ____result.Comparer = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class GenericStructFormatter : global::MessagePack.Formatters.IMessagePackFormatter> - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.GenericStruct value, global::MessagePack.MessagePackSerializerOptions options) - { - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - writer.WriteArrayHeader(2); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.MyProperty0, options); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.MyProperty1, options); - } - - public global::SharedData.GenericStruct Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - throw new global::System.InvalidOperationException("typecode is null, struct not supported"); - } - - options.Security.DepthStep(ref reader); - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - var length = reader.ReadArrayHeader(); - var ____result = new global::SharedData.GenericStruct(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - ____result.MyProperty0 = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - case 1: - ____result.MyProperty1 = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class HolderV0Formatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.HolderV0 value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - writer.WriteArrayHeader(2); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.MyProperty1, options); - writer.Write(value.After); - } - - public global::SharedData.HolderV0 Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - var length = reader.ReadArrayHeader(); - var ____result = new global::SharedData.HolderV0(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - ____result.MyProperty1 = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - case 1: - ____result.After = reader.ReadInt32(); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class HolderV1Formatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.HolderV1 value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - writer.WriteArrayHeader(2); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.MyProperty1, options); - writer.Write(value.After); - } - - public global::SharedData.HolderV1 Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - var length = reader.ReadArrayHeader(); - var ____result = new global::SharedData.HolderV1(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - ____result.MyProperty1 = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - case 1: - ____result.After = reader.ReadInt32(); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class HolderV2Formatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.HolderV2 value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - writer.WriteArrayHeader(2); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.MyProperty1, options); - writer.Write(value.After); - } - - public global::SharedData.HolderV2 Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - var length = reader.ReadArrayHeader(); - var ____result = new global::SharedData.HolderV2(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - ____result.MyProperty1 = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - case 1: - ____result.After = reader.ReadInt32(); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class MyClassFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.MyClass value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - writer.WriteArrayHeader(3); - writer.Write(value.MyProperty1); - writer.Write(value.MyProperty2); - writer.Write(value.MyProperty3); - } - - public global::SharedData.MyClass Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadArrayHeader(); - var ____result = new global::SharedData.MyClass(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - ____result.MyProperty1 = reader.ReadInt32(); - break; - case 1: - ____result.MyProperty2 = reader.ReadInt32(); - break; - case 2: - ____result.MyProperty3 = reader.ReadInt32(); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class MySubUnion1Formatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.MySubUnion1 value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - writer.WriteArrayHeader(4); - writer.WriteNil(); - writer.WriteNil(); - writer.WriteNil(); - writer.Write(value.One); - } - - public global::SharedData.MySubUnion1 Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadArrayHeader(); - var ____result = new global::SharedData.MySubUnion1(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 3: - ____result.One = reader.ReadInt32(); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class MySubUnion2Formatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.MySubUnion2 value, global::MessagePack.MessagePackSerializerOptions options) - { - writer.WriteArrayHeader(6); - writer.WriteNil(); - writer.WriteNil(); - writer.WriteNil(); - writer.WriteNil(); - writer.WriteNil(); - writer.Write(value.Two); - } - - public global::SharedData.MySubUnion2 Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - throw new global::System.InvalidOperationException("typecode is null, struct not supported"); - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadArrayHeader(); - var ____result = new global::SharedData.MySubUnion2(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 5: - ____result.Two = reader.ReadInt32(); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class MySubUnion3Formatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.MySubUnion3 value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - writer.WriteArrayHeader(3); - writer.WriteNil(); - writer.WriteNil(); - writer.Write(value.Three); - } - - public global::SharedData.MySubUnion3 Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadArrayHeader(); - var ____result = new global::SharedData.MySubUnion3(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 2: - ____result.Three = reader.ReadInt32(); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class MySubUnion4Formatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.MySubUnion4 value, global::MessagePack.MessagePackSerializerOptions options) - { - writer.WriteArrayHeader(8); - writer.WriteNil(); - writer.WriteNil(); - writer.WriteNil(); - writer.WriteNil(); - writer.WriteNil(); - writer.WriteNil(); - writer.WriteNil(); - writer.Write(value.Four); - } - - public global::SharedData.MySubUnion4 Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - throw new global::System.InvalidOperationException("typecode is null, struct not supported"); - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadArrayHeader(); - var ____result = new global::SharedData.MySubUnion4(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 7: - ____result.Four = reader.ReadInt32(); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class NestParent_NestContractFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.NestParent.NestContract value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - writer.WriteArrayHeader(1); - writer.Write(value.MyProperty); - } - - public global::SharedData.NestParent.NestContract Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadArrayHeader(); - var ____result = new global::SharedData.NestParent.NestContract(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - ____result.MyProperty = reader.ReadInt32(); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class NonEmpty1Formatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.NonEmpty1 value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - writer.WriteArrayHeader(1); - writer.Write(value.MyProperty); - } - - public global::SharedData.NonEmpty1 Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadArrayHeader(); - var ____result = new global::SharedData.NonEmpty1(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - ____result.MyProperty = reader.ReadInt32(); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class SimpleIntKeyDataFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.SimpleIntKeyData value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - writer.WriteArrayHeader(7); - writer.Write(value.Prop1); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.Prop2, options); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.Prop3, options); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.Prop4, options); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.Prop5, options); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.Prop6, options); - writer.Write(value.BytesSpecial); - } - - public global::SharedData.SimpleIntKeyData Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - var length = reader.ReadArrayHeader(); - var ____result = new global::SharedData.SimpleIntKeyData(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - ____result.Prop1 = reader.ReadInt32(); - break; - case 1: - ____result.Prop2 = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - case 2: - ____result.Prop3 = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - case 3: - ____result.Prop4 = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - case 4: - ____result.Prop5 = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - case 5: - ____result.Prop6 = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - case 6: - ____result.BytesSpecial = global::MessagePack.Internal.CodeGenHelpers.GetArrayFromNullableSequence(reader.ReadBytes()); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class SimpleStructIntKeyDataFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.SimpleStructIntKeyData value, global::MessagePack.MessagePackSerializerOptions options) - { - writer.WriteArrayHeader(3); - writer.Write(value.X); - writer.Write(value.Y); - writer.Write(value.BytesSpecial); - } - - public global::SharedData.SimpleStructIntKeyData Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - throw new global::System.InvalidOperationException("typecode is null, struct not supported"); - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadArrayHeader(); - var ____result = new global::SharedData.SimpleStructIntKeyData(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - ____result.X = reader.ReadInt32(); - break; - case 1: - ____result.Y = reader.ReadInt32(); - break; - case 2: - ____result.BytesSpecial = global::MessagePack.Internal.CodeGenHelpers.GetArrayFromNullableSequence(reader.ReadBytes()); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class SubUnionType1Formatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.SubUnionType1 value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - writer.WriteArrayHeader(2); - writer.Write(value.MyProperty); - writer.Write(value.MyProperty1); - } - - public global::SharedData.SubUnionType1 Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadArrayHeader(); - var ____result = new global::SharedData.SubUnionType1(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - ____result.MyProperty = reader.ReadInt32(); - break; - case 1: - ____result.MyProperty1 = reader.ReadInt32(); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class SubUnionType2Formatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.SubUnionType2 value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - writer.WriteArrayHeader(2); - writer.Write(value.MyProperty); - writer.Write(value.MyProperty2); - } - - public global::SharedData.SubUnionType2 Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadArrayHeader(); - var ____result = new global::SharedData.SubUnionType2(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - ____result.MyProperty = reader.ReadInt32(); - break; - case 1: - ____result.MyProperty2 = reader.ReadInt32(); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class UnVersionBlockTestFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.UnVersionBlockTest value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - writer.WriteArrayHeader(3); - writer.Write(value.MyProperty); - writer.WriteNil(); - writer.Write(value.MyProperty2); - } - - public global::SharedData.UnVersionBlockTest Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadArrayHeader(); - var ____result = new global::SharedData.UnVersionBlockTest(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - ____result.MyProperty = reader.ReadInt32(); - break; - case 2: - ____result.MyProperty2 = reader.ReadInt32(); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class Vector2Formatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.Vector2 value, global::MessagePack.MessagePackSerializerOptions options) - { - writer.WriteArrayHeader(2); - writer.Write(value.X); - writer.Write(value.Y); - } - - public global::SharedData.Vector2 Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - throw new global::System.InvalidOperationException("typecode is null, struct not supported"); - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadArrayHeader(); - var __X__ = default(float); - var __Y__ = default(float); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - __X__ = reader.ReadSingle(); - break; - case 1: - __Y__ = reader.ReadSingle(); - break; - default: - reader.Skip(); - break; - } - } - - var ____result = new global::SharedData.Vector2(__X__, __Y__); - reader.Depth--; - return ____result; - } - } - - public sealed class Vector3LikeFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.Vector3Like value, global::MessagePack.MessagePackSerializerOptions options) - { - writer.WriteArrayHeader(3); - writer.Write(value.x); - writer.Write(value.y); - writer.Write(value.z); - } - - public global::SharedData.Vector3Like Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - throw new global::System.InvalidOperationException("typecode is null, struct not supported"); - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadArrayHeader(); - var __x__ = default(float); - var __y__ = default(float); - var __z__ = default(float); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - __x__ = reader.ReadSingle(); - break; - case 1: - __y__ = reader.ReadSingle(); - break; - case 2: - __z__ = reader.ReadSingle(); - break; - default: - reader.Skip(); - break; - } - } - - var ____result = new global::SharedData.Vector3Like(__x__, __y__, __z__); - reader.Depth--; - return ____result; - } - } - - public sealed class VectorLike2Formatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.VectorLike2 value, global::MessagePack.MessagePackSerializerOptions options) - { - writer.WriteArrayHeader(2); - writer.Write(value.x); - writer.Write(value.y); - } - - public global::SharedData.VectorLike2 Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - throw new global::System.InvalidOperationException("typecode is null, struct not supported"); - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadArrayHeader(); - var __x__ = default(float); - var __y__ = default(float); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - __x__ = reader.ReadSingle(); - break; - case 1: - __y__ = reader.ReadSingle(); - break; - default: - reader.Skip(); - break; - } - } - - var ____result = new global::SharedData.VectorLike2(__x__, __y__); - reader.Depth--; - return ____result; - } - } - - public sealed class Version0Formatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.Version0 value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - writer.WriteArrayHeader(4); - writer.WriteNil(); - writer.WriteNil(); - writer.WriteNil(); - writer.Write(value.MyProperty1); - } - - public global::SharedData.Version0 Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadArrayHeader(); - var ____result = new global::SharedData.Version0(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 3: - ____result.MyProperty1 = reader.ReadInt32(); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class Version1Formatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.Version1 value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - writer.WriteArrayHeader(6); - writer.WriteNil(); - writer.WriteNil(); - writer.WriteNil(); - writer.Write(value.MyProperty1); - writer.Write(value.MyProperty2); - writer.Write(value.MyProperty3); - } - - public global::SharedData.Version1 Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadArrayHeader(); - var ____result = new global::SharedData.Version1(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 3: - ____result.MyProperty1 = reader.ReadInt32(); - break; - case 4: - ____result.MyProperty2 = reader.ReadInt32(); - break; - case 5: - ____result.MyProperty3 = reader.ReadInt32(); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class Version2Formatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.Version2 value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - writer.WriteArrayHeader(8); - writer.WriteNil(); - writer.WriteNil(); - writer.WriteNil(); - writer.Write(value.MyProperty1); - writer.Write(value.MyProperty2); - writer.Write(value.MyProperty3); - writer.WriteNil(); - writer.Write(value.MyProperty5); - } - - public global::SharedData.Version2 Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadArrayHeader(); - var ____result = new global::SharedData.Version2(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 3: - ____result.MyProperty1 = reader.ReadInt32(); - break; - case 4: - ____result.MyProperty2 = reader.ReadInt32(); - break; - case 5: - ____result.MyProperty3 = reader.ReadInt32(); - break; - case 7: - ____result.MyProperty5 = reader.ReadInt32(); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class VersionBlockTestFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.VersionBlockTest value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - writer.WriteArrayHeader(3); - writer.Write(value.MyProperty); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.UnknownBlock, options); - writer.Write(value.MyProperty2); - } - - public global::SharedData.VersionBlockTest Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - var length = reader.ReadArrayHeader(); - var ____result = new global::SharedData.VersionBlockTest(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - ____result.MyProperty = reader.ReadInt32(); - break; - case 1: - ____result.UnknownBlock = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - case 2: - ____result.MyProperty2 = reader.ReadInt32(); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class VersioningUnionFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.VersioningUnion value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - writer.WriteArrayHeader(8); - writer.WriteNil(); - writer.WriteNil(); - writer.WriteNil(); - writer.WriteNil(); - writer.WriteNil(); - writer.WriteNil(); - writer.WriteNil(); - writer.Write(value.FV); - } - - public global::SharedData.VersioningUnion Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadArrayHeader(); - var ____result = new global::SharedData.VersioningUnion(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 7: - ____result.FV = reader.ReadInt32(); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class WithIndexerFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.WithIndexer value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value == null) - { - writer.WriteNil(); - return; - } - - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - writer.WriteArrayHeader(2); - writer.Write(value.Data1); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.Data2, options); - } - - public global::SharedData.WithIndexer Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; - var length = reader.ReadArrayHeader(); - var ____result = new global::SharedData.WithIndexer(); - - for (int i = 0; i < length; i++) - { - switch (i) - { - case 0: - ____result.Data1 = reader.ReadInt32(); - break; - case 1: - ____result.Data2 = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - break; - default: - reader.Skip(); - break; - } - } - - reader.Depth--; - return ____result; - } - } - -} - -#pragma warning restore 168 -#pragma warning restore 414 -#pragma warning restore 618 -#pragma warning restore 612 - -#pragma warning restore SA1129 // Do not use default value type constructor -#pragma warning restore SA1309 // Field names should not begin with underscore -#pragma warning restore SA1312 // Variable names should begin with lower-case letter -#pragma warning restore SA1403 // File may only contain a single namespace -#pragma warning restore SA1649 // File name should match first type name - -// -// THIS (.cs) FILE IS GENERATED BY MPC(MessagePack-CSharp). DO NOT CHANGE IT. -// - -#pragma warning disable 618 -#pragma warning disable 612 -#pragma warning disable 414 -#pragma warning disable 168 -#pragma warning disable CS1591 // document public APIs - -#pragma warning disable SA1129 // Do not use default value type constructor -#pragma warning disable SA1309 // Field names should not begin with underscore -#pragma warning disable SA1312 // Variable names should begin with lower-case letter -#pragma warning disable SA1403 // File may only contain a single namespace -#pragma warning disable SA1649 // File name should match first type name - -namespace MessagePack.Formatters.SharedData -{ - public sealed class Callback2Formatter : global::MessagePack.Formatters.IMessagePackFormatter - { - // X - private static global::System.ReadOnlySpan GetSpan_X() => new byte[1 + 1] { 161, 88 }; - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.Callback2 value, global::MessagePack.MessagePackSerializerOptions options) - { - value.OnBeforeSerialize(); - writer.WriteMapHeader(1); - writer.WriteRaw(GetSpan_X()); - writer.Write(value.X); - } - - public global::SharedData.Callback2 Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - throw new global::System.InvalidOperationException("typecode is null, struct not supported"); - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadMapHeader(); - var __X__ = default(int); - - for (int i = 0; i < length; i++) - { - var stringKey = global::MessagePack.Internal.CodeGenHelpers.ReadStringSpan(ref reader); - switch (stringKey.Length) - { - default: - FAIL: - reader.Skip(); - continue; - case 1: - if (stringKey[0] != 88) { goto FAIL; } - - __X__ = reader.ReadInt32(); - continue; - - } - } - - var ____result = new global::SharedData.Callback2(__X__); - ____result.OnAfterDeserialize(); - reader.Depth--; - return ____result; - } - } - - public sealed class Callback2_2Formatter : global::MessagePack.Formatters.IMessagePackFormatter - { - // X - private static global::System.ReadOnlySpan GetSpan_X() => new byte[1 + 1] { 161, 88 }; - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.Callback2_2 value, global::MessagePack.MessagePackSerializerOptions options) - { - ((global::MessagePack.IMessagePackSerializationCallbackReceiver)value).OnBeforeSerialize(); - writer.WriteMapHeader(1); - writer.WriteRaw(GetSpan_X()); - writer.Write(value.X); - } - - public global::SharedData.Callback2_2 Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - throw new global::System.InvalidOperationException("typecode is null, struct not supported"); - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadMapHeader(); - var __X__ = default(int); - - for (int i = 0; i < length; i++) - { - var stringKey = global::MessagePack.Internal.CodeGenHelpers.ReadStringSpan(ref reader); - switch (stringKey.Length) - { - default: - FAIL: - reader.Skip(); - continue; - case 1: - if (stringKey[0] != 88) { goto FAIL; } - - __X__ = reader.ReadInt32(); - continue; - - } - } - - var ____result = new global::SharedData.Callback2_2(__X__); - ((global::MessagePack.IMessagePackSerializationCallbackReceiver)____result).OnAfterDeserialize(); - reader.Depth--; - return ____result; - } - } - - public sealed class DefaultValueStringKeyClassWithExplicitConstructorFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - // Prop1 - private static global::System.ReadOnlySpan GetSpan_Prop1() => new byte[1 + 5] { 165, 80, 114, 111, 112, 49 }; - // Prop2 - private static global::System.ReadOnlySpan GetSpan_Prop2() => new byte[1 + 5] { 165, 80, 114, 111, 112, 50 }; - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.DefaultValueStringKeyClassWithExplicitConstructor value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value is null) - { - writer.WriteNil(); - return; - } - - writer.WriteMapHeader(2); - writer.WriteRaw(GetSpan_Prop1()); - writer.Write(value.Prop1); - writer.WriteRaw(GetSpan_Prop2()); - writer.Write(value.Prop2); - } - - public global::SharedData.DefaultValueStringKeyClassWithExplicitConstructor Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadMapHeader(); - var __Prop1__ = default(int); - var __Prop2__IsInitialized = false; - var __Prop2__ = default(int); - - for (int i = 0; i < length; i++) - { - var stringKey = global::MessagePack.Internal.CodeGenHelpers.ReadStringSpan(ref reader); - switch (stringKey.Length) - { - default: - FAIL: - reader.Skip(); - continue; - case 5: - switch (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey)) - { - default: goto FAIL; - case 212339749456UL: - __Prop1__ = reader.ReadInt32(); - continue; - case 216634716752UL: - __Prop2__IsInitialized = true; - __Prop2__ = reader.ReadInt32(); - continue; - } - - } - } - - var ____result = new global::SharedData.DefaultValueStringKeyClassWithExplicitConstructor(__Prop1__); - if (__Prop2__IsInitialized) - { - ____result.Prop2 = __Prop2__; - } - - reader.Depth--; - return ____result; - } - } - - public sealed class DefaultValueStringKeyClassWithoutExplicitConstructorFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - // Prop1 - private static global::System.ReadOnlySpan GetSpan_Prop1() => new byte[1 + 5] { 165, 80, 114, 111, 112, 49 }; - // Prop2 - private static global::System.ReadOnlySpan GetSpan_Prop2() => new byte[1 + 5] { 165, 80, 114, 111, 112, 50 }; - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.DefaultValueStringKeyClassWithoutExplicitConstructor value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value is null) - { - writer.WriteNil(); - return; - } - - writer.WriteMapHeader(2); - writer.WriteRaw(GetSpan_Prop1()); - writer.Write(value.Prop1); - writer.WriteRaw(GetSpan_Prop2()); - writer.Write(value.Prop2); - } - - public global::SharedData.DefaultValueStringKeyClassWithoutExplicitConstructor Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadMapHeader(); - var ____result = new global::SharedData.DefaultValueStringKeyClassWithoutExplicitConstructor(); - - for (int i = 0; i < length; i++) - { - var stringKey = global::MessagePack.Internal.CodeGenHelpers.ReadStringSpan(ref reader); - switch (stringKey.Length) - { - default: - FAIL: - reader.Skip(); - continue; - case 5: - switch (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey)) - { - default: goto FAIL; - case 212339749456UL: - ____result.Prop1 = reader.ReadInt32(); - continue; - case 216634716752UL: - ____result.Prop2 = reader.ReadInt32(); - continue; - } - - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class DefaultValueStringKeyStructWithExplicitConstructorFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - // Prop1 - private static global::System.ReadOnlySpan GetSpan_Prop1() => new byte[1 + 5] { 165, 80, 114, 111, 112, 49 }; - // Prop2 - private static global::System.ReadOnlySpan GetSpan_Prop2() => new byte[1 + 5] { 165, 80, 114, 111, 112, 50 }; - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.DefaultValueStringKeyStructWithExplicitConstructor value, global::MessagePack.MessagePackSerializerOptions options) - { - writer.WriteMapHeader(2); - writer.WriteRaw(GetSpan_Prop1()); - writer.Write(value.Prop1); - writer.WriteRaw(GetSpan_Prop2()); - writer.Write(value.Prop2); - } - - public global::SharedData.DefaultValueStringKeyStructWithExplicitConstructor Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - throw new global::System.InvalidOperationException("typecode is null, struct not supported"); - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadMapHeader(); - var __Prop1__ = default(int); - var __Prop2__IsInitialized = false; - var __Prop2__ = default(int); - - for (int i = 0; i < length; i++) - { - var stringKey = global::MessagePack.Internal.CodeGenHelpers.ReadStringSpan(ref reader); - switch (stringKey.Length) - { - default: - FAIL: - reader.Skip(); - continue; - case 5: - switch (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey)) - { - default: goto FAIL; - case 212339749456UL: - __Prop1__ = reader.ReadInt32(); - continue; - case 216634716752UL: - __Prop2__IsInitialized = true; - __Prop2__ = reader.ReadInt32(); - continue; - } - - } - } - - var ____result = new global::SharedData.DefaultValueStringKeyStructWithExplicitConstructor(__Prop1__); - if (__Prop2__IsInitialized) - { - ____result.Prop2 = __Prop2__; - } - - reader.Depth--; - return ____result; - } - } - - public sealed class Empty2Formatter : global::MessagePack.Formatters.IMessagePackFormatter - { - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.Empty2 value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value is null) - { - writer.WriteNil(); - return; - } - - writer.WriteMapHeader(0); - } - - public global::SharedData.Empty2 Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - reader.Skip(); - var ____result = new global::SharedData.Empty2(); - return ____result; - } - } - - public sealed class GenericConstrainedClassStringKeyFormatter : global::MessagePack.Formatters.IMessagePackFormatter> - where T1 : class - where T2 : class, global::System.Collections.Generic.IEqualityComparer - { - // MyProperty0 - private static global::System.ReadOnlySpan GetSpan_MyProperty0() => new byte[1 + 11] { 171, 77, 121, 80, 114, 111, 112, 101, 114, 116, 121, 48 }; - // Comparer - private static global::System.ReadOnlySpan GetSpan_Comparer() => new byte[1 + 8] { 168, 67, 111, 109, 112, 97, 114, 101, 114 }; - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.GenericConstrainedClassStringKey value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value is null) - { - writer.WriteNil(); - return; - } - - var formatterResolver = options.Resolver; - writer.WriteMapHeader(2); - writer.WriteRaw(GetSpan_MyProperty0()); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.MyProperty0, options); - writer.WriteRaw(GetSpan_Comparer()); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.Comparer, options); - } - - public global::SharedData.GenericConstrainedClassStringKey Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var formatterResolver = options.Resolver; - var length = reader.ReadMapHeader(); - var ____result = new global::SharedData.GenericConstrainedClassStringKey(); - - for (int i = 0; i < length; i++) - { - var stringKey = global::MessagePack.Internal.CodeGenHelpers.ReadStringSpan(ref reader); - switch (stringKey.Length) - { - default: - FAIL: - reader.Skip(); - continue; - case 11: - if (!global::System.MemoryExtensions.SequenceEqual(stringKey, GetSpan_MyProperty0().Slice(1))) { goto FAIL; } - - ____result.MyProperty0 = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - continue; - case 8: - if (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey) != 8243120455795175235UL) { goto FAIL; } - - ____result.Comparer = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - continue; - - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class GenericConstrainedStructStringKeyFormatter : global::MessagePack.Formatters.IMessagePackFormatter> - where T1 : unmanaged - where T2 : unmanaged, global::System.Collections.Generic.IEqualityComparer - { - // MyProperty0 - private static global::System.ReadOnlySpan GetSpan_MyProperty0() => new byte[1 + 11] { 171, 77, 121, 80, 114, 111, 112, 101, 114, 116, 121, 48 }; - // Comparer - private static global::System.ReadOnlySpan GetSpan_Comparer() => new byte[1 + 8] { 168, 67, 111, 109, 112, 97, 114, 101, 114 }; - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.GenericConstrainedStructStringKey value, global::MessagePack.MessagePackSerializerOptions options) - { - var formatterResolver = options.Resolver; - writer.WriteMapHeader(2); - writer.WriteRaw(GetSpan_MyProperty0()); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.MyProperty0, options); - writer.WriteRaw(GetSpan_Comparer()); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.Comparer, options); - } - - public global::SharedData.GenericConstrainedStructStringKey Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - throw new global::System.InvalidOperationException("typecode is null, struct not supported"); - } - - options.Security.DepthStep(ref reader); - var formatterResolver = options.Resolver; - var length = reader.ReadMapHeader(); - var ____result = new global::SharedData.GenericConstrainedStructStringKey(); - - for (int i = 0; i < length; i++) - { - var stringKey = global::MessagePack.Internal.CodeGenHelpers.ReadStringSpan(ref reader); - switch (stringKey.Length) - { - default: - FAIL: - reader.Skip(); - continue; - case 11: - if (!global::System.MemoryExtensions.SequenceEqual(stringKey, GetSpan_MyProperty0().Slice(1))) { goto FAIL; } - - ____result.MyProperty0 = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - continue; - case 8: - if (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey) != 8243120455795175235UL) { goto FAIL; } - - ____result.Comparer = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - continue; - - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class NonEmpty2Formatter : global::MessagePack.Formatters.IMessagePackFormatter - { - // MyProperty - private static global::System.ReadOnlySpan GetSpan_MyProperty() => new byte[1 + 10] { 170, 77, 121, 80, 114, 111, 112, 101, 114, 116, 121 }; - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.NonEmpty2 value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value is null) - { - writer.WriteNil(); - return; - } - - writer.WriteMapHeader(1); - writer.WriteRaw(GetSpan_MyProperty()); - writer.Write(value.MyProperty); - } - - public global::SharedData.NonEmpty2 Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var length = reader.ReadMapHeader(); - var ____result = new global::SharedData.NonEmpty2(); - - for (int i = 0; i < length; i++) - { - var stringKey = global::MessagePack.Internal.CodeGenHelpers.ReadStringSpan(ref reader); - switch (stringKey.Length) - { - default: - FAIL: - reader.Skip(); - continue; - case 10: - if (!global::System.MemoryExtensions.SequenceEqual(stringKey, GetSpan_MyProperty().Slice(1))) { goto FAIL; } - - ____result.MyProperty = reader.ReadInt32(); - continue; - - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class SimpleStringKeyDataFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - // Prop1 - private static global::System.ReadOnlySpan GetSpan_Prop1() => new byte[1 + 5] { 165, 80, 114, 111, 112, 49 }; - // Prop2 - private static global::System.ReadOnlySpan GetSpan_Prop2() => new byte[1 + 5] { 165, 80, 114, 111, 112, 50 }; - // Prop3 - private static global::System.ReadOnlySpan GetSpan_Prop3() => new byte[1 + 5] { 165, 80, 114, 111, 112, 51 }; - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.SimpleStringKeyData value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value is null) - { - writer.WriteNil(); - return; - } - - var formatterResolver = options.Resolver; - writer.WriteMapHeader(3); - writer.WriteRaw(GetSpan_Prop1()); - writer.Write(value.Prop1); - writer.WriteRaw(GetSpan_Prop2()); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.Prop2, options); - writer.WriteRaw(GetSpan_Prop3()); - writer.Write(value.Prop3); - } - - public global::SharedData.SimpleStringKeyData Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var formatterResolver = options.Resolver; - var length = reader.ReadMapHeader(); - var ____result = new global::SharedData.SimpleStringKeyData(); - - for (int i = 0; i < length; i++) - { - var stringKey = global::MessagePack.Internal.CodeGenHelpers.ReadStringSpan(ref reader); - switch (stringKey.Length) - { - default: - FAIL: - reader.Skip(); - continue; - case 5: - switch (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey)) - { - default: goto FAIL; - case 212339749456UL: - ____result.Prop1 = reader.ReadInt32(); - continue; - case 216634716752UL: - ____result.Prop2 = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - continue; - case 220929684048UL: - ____result.Prop3 = reader.ReadInt32(); - continue; - } - - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class SimpleStructStringKeyDataFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - // key-X - private static global::System.ReadOnlySpan GetSpan_X() => new byte[1 + 5] { 165, 107, 101, 121, 45, 88 }; - // key-Y - private static global::System.ReadOnlySpan GetSpan_Y() => new byte[1 + 5] { 165, 107, 101, 121, 45, 89 }; - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::SharedData.SimpleStructStringKeyData value, global::MessagePack.MessagePackSerializerOptions options) - { - var formatterResolver = options.Resolver; - writer.WriteMapHeader(2); - writer.WriteRaw(GetSpan_X()); - writer.Write(value.X); - writer.WriteRaw(GetSpan_Y()); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.Y, options); - } - - public global::SharedData.SimpleStructStringKeyData Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - throw new global::System.InvalidOperationException("typecode is null, struct not supported"); - } - - options.Security.DepthStep(ref reader); - var formatterResolver = options.Resolver; - var length = reader.ReadMapHeader(); - var ____result = new global::SharedData.SimpleStructStringKeyData(); - - for (int i = 0; i < length; i++) - { - var stringKey = global::MessagePack.Internal.CodeGenHelpers.ReadStringSpan(ref reader); - switch (stringKey.Length) - { - default: - FAIL: - reader.Skip(); - continue; - case 5: - switch (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey)) - { - default: goto FAIL; - case 378720052587UL: - ____result.X = reader.ReadInt32(); - continue; - case 383015019883UL: - ____result.Y = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - continue; - } - - } - } - - reader.Depth--; - return ____result; - } - } - -} - -#pragma warning restore 168 -#pragma warning restore 414 -#pragma warning restore 618 -#pragma warning restore 612 - -#pragma warning restore SA1129 // Do not use default value type constructor -#pragma warning restore SA1309 // Field names should not begin with underscore -#pragma warning restore SA1312 // Variable names should begin with lower-case letter -#pragma warning restore SA1403 // File may only contain a single namespace -#pragma warning restore SA1649 // File name should match first type name - diff --git a/sandbox/Sandbox/GeneratedMessagePackResolver.cs b/sandbox/Sandbox/GeneratedMessagePackResolver.cs new file mode 100644 index 000000000..85ed11ef8 --- /dev/null +++ b/sandbox/Sandbox/GeneratedMessagePackResolver.cs @@ -0,0 +1,11 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using MessagePack; + +namespace Sandbox; + +[GeneratedMessagePackResolver] +public partial class GeneratedMessagePackResolver +{ +} diff --git a/sandbox/Sandbox/Program.cs b/sandbox/Sandbox/Program.cs index 9d9ce7e03..d787a3247 100644 --- a/sandbox/Sandbox/Program.cs +++ b/sandbox/Sandbox/Program.cs @@ -2,879 +2,33 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; -using System.Buffers; -using System.Collections; -using System.Collections.Concurrent; using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Reflection; -using System.Text; +using System.Collections.ObjectModel; using MessagePack; -using MessagePack.Formatters; -using MessagePack.Internal; -using MessagePack.Resolvers; -using Newtonsoft.Json; -using ProtoBuf; -using SharedData; -using UnityEngine; -using ZeroFormatter; -#pragma warning disable SA1307 // Accessible fields should begin with upper-case letter -#pragma warning disable SA1401 // Fields should be private -#pragma warning disable SA1402 // File may only contain a single type -#pragma warning disable SA1649 // File name should match first type name +Console.WriteLine("foo"); -namespace Sandbox -{ - [ZeroFormattable] - [ProtoBuf.ProtoContract] - [MessagePackObject] - public class Person : IEquatable - { - [Index(0)] - [Key(0)] - [MsgPack.Serialization.MessagePackMember(0)] - [ProtoMember(1)] - public virtual int Age { get; set; } - - [Index(1)] - [Key(1)] - [MsgPack.Serialization.MessagePackMember(1)] - [ProtoMember(2)] - public virtual string FirstName { get; set; } - - [Index(2)] - [Key(2)] - [MsgPack.Serialization.MessagePackMember(2)] - [ProtoMember(3)] - public virtual string LastName { get; set; } - - [Index(3)] - [MsgPack.Serialization.MessagePackMember(3)] - [Key(3)] - [ProtoMember(4)] - public virtual Sex Sex { get; set; } - - public bool Equals(Person other) - { - return this.Age == other.Age && this.FirstName == other.FirstName && this.LastName == other.LastName && this.Sex == other.Sex; - } - } - - public enum Sex : sbyte - { - Unknown, - Male, - Female, - } - - public class TestCollection : ICollection - { - public List internalCollection = new List(); - - public int Count => this.internalCollection.Count; - - public bool IsReadOnly => throw new NotImplementedException(); - - public void Add(T item) - { - this.internalCollection.Add(item); - } - - public void Clear() - { - throw new NotImplementedException(); - } - - public bool Contains(T item) - { - throw new NotImplementedException(); - } - - public void CopyTo(T[] array, int arrayIndex) - { - throw new NotImplementedException(); - } - - public IEnumerator GetEnumerator() - { - return this.internalCollection.GetEnumerator(); - } - - public bool Remove(T item) - { - throw new NotImplementedException(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - throw new NotImplementedException(); - } - } - - [MessagePackObject(true)] - public class Takox - { -#pragma warning disable SA1300 // Element should begin with upper-case letter - public int hoga { get; set; } - - public int huga { get; set; } - - public int tako { get; set; } -#pragma warning restore SA1300 // Element should begin with upper-case letter - } - - [MessagePackObject] - public class MyClass - { - // Key is serialization index, it is important for versioning. - [Key(0)] - public int Age { get; set; } - - [Key(1)] - public string FirstName { get; set; } - - [Key(2)] - public string LastName { get; set; } - - // public members and does not serialize target, mark IgnoreMemberttribute - [IgnoreMember] - public string FullName => this.FirstName + this.LastName; - } - - [MessagePackObject(keyAsPropertyName: true)] - public class Sample1 - { - [Key(0)] - public int Foo { get; set; } - - [Key(1)] - public int Bar { get; set; } - } - - [MessagePackObject] - public class Sample2 - { - [Key("foo")] - public int Foo { get; set; } - - [Key("bar")] - public int Bar { get; set; } - } - - [MessagePackObject] - public class IntKeySample - { - [Key(3)] - public int A { get; set; } - - [Key(10)] - public int B { get; set; } - } - - public class ContractlessSample - { - public int MyProperty1 { get; set; } - - public int MyProperty2 { get; set; } - } - - [MessagePackObject] - public class SampleCallback : IMessagePackSerializationCallbackReceiver - { - [Key(0)] - public int Key { get; set; } - - public void OnBeforeSerialize() - { - Console.WriteLine("OnBefore"); - } +//[MessagePackObject] +//public class Foo +//{ +// [Key(0)] +// public T Member { get; set; } +//} - public void OnAfterDeserialize() - { - Console.WriteLine("OnAfter"); - } - } - - [MessagePackObject] - public struct Point - { - [Key(0)] - public readonly int X; - [Key(1)] - public readonly int Y; - - // can't find matched constructor parameter, parameterType mismatch. type:Point parameterIndex:0 parameterType:ValueTuple`2 - public Point((int, int) p) - { - this.X = p.Item1; - this.Y = p.Item2; - } - - [SerializationConstructor] - public Point(int x, int y) - { - this.X = x; - this.Y = y; - } - } - - // mark inheritance types - [MessagePack.Union(0, typeof(FooClass))] - [MessagePack.Union(100, typeof(BarClass))] - public interface IUnionSample - { - } - - [MessagePackObject] - public class FooClass : IUnionSample - { - [Key(0)] - public int XYZ { get; set; } - } - - [MessagePackObject] - public class BarClass : IUnionSample - { - [Key(0)] - public string OPQ { get; set; } - } - - [MessagePackFormatter(typeof(CustomObjectFormatter))] - public class CustomObject - { - private string internalId; - - public CustomObject() - { - this.internalId = Guid.NewGuid().ToString(); - } - - // serialize/deserialize internal field. - private class CustomObjectFormatter : IMessagePackFormatter - { - public void Serialize(ref MessagePackWriter writer, CustomObject value, MessagePackSerializerOptions options) - { - options.Resolver.GetFormatterWithVerify().Serialize(ref writer, value.internalId, options); - } - - public CustomObject Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) - { - var id = options.Resolver.GetFormatterWithVerify().Deserialize(ref reader, options); - return new CustomObject { internalId = id }; - } - } - } - - public interface IEntity - { - string Name { get; } - } - - public class Event : IEntity - { - public Event(string name) - { - this.Name = name; - } +//[MessagePackObject] +//public class Bar +//{ +// [Key(0)] +// public Foo MemberUserGeneric { get; set; } - public string Name { get; } - } - - public class Holder - { - public Holder(IEntity entity) - { - this.Entity = entity; - } - - public IEntity Entity { get; } - } - - public class Dummy___ - { - public MethodBase MyProperty { get; set; } - } - - [MessagePackObject] - public class Callback1 : IMessagePackSerializationCallbackReceiver - { - [Key(0)] - public int X { get; set; } - - [IgnoreMember] - public bool CalledBefore { get; private set; } - - [IgnoreMember] - public bool CalledAfter { get; private set; } - - public Callback1(int x) - { - } +// [Key(1)] +// public System.Collections.Generic.List MemberKnownGeneric { get; set; } +//} - public void OnBeforeSerialize() - { - this.CalledBefore = true; - } - public void OnAfterDeserialize() - { - this.CalledAfter = true; - } - } - - [MessagePackObject] - public class SimpleIntKeyData - { - [Key(0)] - ////[MessagePackFormatter(typeof(OreOreFormatter))] - public int Prop1 { get; set; } - - [Key(1)] - public ByteEnum Prop2 { get; set; } - - [Key(2)] - public string Prop3 { get; set; } - - [Key(3)] - public SimpleStringKeyData Prop4 { get; set; } - - [Key(4)] - public SimpleStructIntKeyData Prop5 { get; set; } - - [Key(5)] - public SimpleStructStringKeyData Prop6 { get; set; } - - [Key(6)] - public byte[] BytesSpecial { get; set; } - - ////[Key(7)] - ////[MessagePackFormatter(typeof(OreOreFormatter2), 100, "hogehoge")] - ////[MessagePackFormatter(typeof(OreOreFormatter))] - ////public int Prop7 { get; set; } - } - - [MessagePack.MessagePackObject(true)] - public class StringKeySerializerTarget2 - { - public int TotalQuestions { get; set; } - - public int TotalUnanswered { get; set; } - - public int QuestionsPerMinute { get; set; } - - public int AnswersPerMinute { get; set; } - - public int TotalVotes { get; set; } - - public int BadgesPerMinute { get; set; } - - public int NewActiveUsers { get; set; } - - public int ApiRevision { get; set; } - - public int Site { get; set; } - } - - internal class Program - { - private static readonly MessagePackSerializerOptions LZ4Standard = MessagePackSerializerOptions.Standard.WithCompression(MessagePackCompression.Lz4Block); - - private static void Main(string[] args) - { - var option = MessagePackSerializerOptions.Standard.WithCompression(MessagePackCompression.Lz4BlockArray); - var data = Enumerable.Range(0, 10000).Select(x => new StringKeySerializerTarget2()).ToArray(); - - var bin = MessagePackSerializer.Serialize(data, option); - Console.WriteLine(bin.Length); - } - - private static void Benchmark(T target) - { - const int Iteration = 10000; // 10000 - - var jsonSerializer = new JsonSerializer(); - MsgPack.Serialization.SerializationContext msgpack = MsgPack.Serialization.SerializationContext.Default; - msgpack.GetSerializer().PackSingleObject(target); - MessagePackSerializer.Serialize(target); - MessagePackSerializer.Serialize(target, LZ4Standard); - ZeroFormatter.ZeroFormatterSerializer.Serialize(target); - ProtoBuf.Serializer.Serialize(new MemoryStream(), target); - jsonSerializer.Serialize(new JsonTextWriter(new StringWriter()), target); - - Console.WriteLine(typeof(T).Name + " serialization test"); - Console.WriteLine(); - - Console.WriteLine("Serialize::"); - byte[] data = null; - byte[] data0 = null; - byte[] data1 = null; - byte[] data2 = null; - byte[] data3 = null; - byte[] dataJson = null; - byte[] dataGzipJson = null; - using (new Measure("MsgPack-Cli")) - { - for (int i = 0; i < Iteration; i++) - { - data = msgpack.GetSerializer().PackSingleObject(target); - } - } - - using (new Measure("MessagePack-CSharp")) - { - for (int i = 0; i < Iteration; i++) - { - data0 = MessagePackSerializer.Serialize(target); - } - } - - using (new Measure("MessagePack(LZ4)")) - { - for (int i = 0; i < Iteration; i++) - { - data3 = MessagePackSerializer.Serialize(target, LZ4Standard); - } - } - - using (new Measure("ZeroFormatter")) - { - for (int i = 0; i < Iteration; i++) - { - data1 = ZeroFormatter.ZeroFormatterSerializer.Serialize(target); - } - } - - using (new Measure("JsonNet")) - { - for (int i = 0; i < Iteration; i++) - { - using (var ms = new MemoryStream()) - using (var sw = new StreamWriter(ms, Encoding.UTF8, 1024, true)) - using (var jw = new JsonTextWriter(sw)) - { - jsonSerializer.Serialize(jw, target); - } - } - } - - using (new Measure("JsonNet+Gzip")) - { - for (int i = 0; i < Iteration; i++) - { - using (var ms = new MemoryStream()) - using (var gzip = new GZipStream(ms, CompressionLevel.Fastest)) - using (var sw = new StreamWriter(gzip, Encoding.UTF8, 1024, true)) - using (var jw = new JsonTextWriter(sw)) - { - jsonSerializer.Serialize(jw, target); - } - } - } - - using (new Measure("protobuf-net")) - { - for (int i = 0; i < Iteration; i++) - { - using (var ms = new MemoryStream()) - { - ProtoBuf.Serializer.Serialize(ms, target); - } - } - } - - using (var ms = new MemoryStream()) - { - ProtoBuf.Serializer.Serialize(ms, target); - data2 = ms.ToArray(); - } - - using (var ms = new MemoryStream()) - { - using (var sw = new StreamWriter(ms, Encoding.UTF8, 1024, true)) - using (var jw = new JsonTextWriter(sw)) - { - jsonSerializer.Serialize(jw, target); - } - - dataJson = ms.ToArray(); - } - - using (var ms = new MemoryStream()) - { - using (var gzip = new GZipStream(ms, CompressionLevel.Fastest)) - using (var sw = new StreamWriter(gzip, Encoding.UTF8, 1024, true)) - using (var jw = new JsonTextWriter(sw)) - { - jsonSerializer.Serialize(jw, target); - } - - dataGzipJson = ms.ToArray(); - } - - msgpack.GetSerializer().UnpackSingleObject(data); - MessagePackSerializer.Deserialize(data0); - ZeroFormatterSerializer.Deserialize(data1); - ProtoBuf.Serializer.Deserialize(new MemoryStream(data2)); - MessagePackSerializer.Deserialize(data3, LZ4Standard); - jsonSerializer.Deserialize(new JsonTextReader(new StreamReader(new MemoryStream(dataJson)))); - - Console.WriteLine(); - Console.WriteLine("Deserialize::"); - - using (new Measure("MsgPack-Cli")) - { - for (int i = 0; i < Iteration; i++) - { - msgpack.GetSerializer().UnpackSingleObject(data); - } - } - - using (new Measure("MessagePack-CSharp")) - { - for (int i = 0; i < Iteration; i++) - { - MessagePackSerializer.Deserialize(data0); - } - } - - using (new Measure("MessagePack(LZ4)")) - { - for (int i = 0; i < Iteration; i++) - { - MessagePackSerializer.Deserialize(data3, LZ4Standard); - } - } - - using (new Measure("ZeroFormatter")) - { - for (int i = 0; i < Iteration; i++) - { - ZeroFormatterSerializer.Deserialize(data1); - } - } - - using (new Measure("JsonNet")) - { - for (int i = 0; i < Iteration; i++) - { - using (var ms = new MemoryStream(dataJson)) - using (var sr = new StreamReader(ms, Encoding.UTF8)) - using (var jr = new JsonTextReader(sr)) - { - jsonSerializer.Deserialize(jr); - } - } - } - - using (new Measure("JsonNet+Gzip")) - { - for (int i = 0; i < Iteration; i++) - { - using (var ms = new MemoryStream(dataGzipJson)) - using (var gzip = new GZipStream(ms, CompressionMode.Decompress)) - using (var sr = new StreamReader(gzip, Encoding.UTF8)) - using (var jr = new JsonTextReader(sr)) - { - jsonSerializer.Deserialize(jr); - } - } - } - - using (new Measure("protobuf-net")) - { - for (int i = 0; i < Iteration; i++) - { - using (var ms = new MemoryStream(data2)) - { - ProtoBuf.Serializer.Deserialize(ms); - } - } - } - - Console.WriteLine(); - Console.WriteLine("FileSize::"); - var label = string.Empty; - label = "MsgPack-Cli"; - Console.WriteLine($"{label,20} {data.Length} Byte"); - label = "MessagePack-CSharp"; - Console.WriteLine($"{label,20} {data0.Length} Byte"); - label = "MessagePack(LZ4)"; - Console.WriteLine($"{label,20} {data3.Length} Byte"); - label = "ZeroFormatter"; - Console.WriteLine($"{label,20} {data1.Length} Byte"); - label = "protobuf-net"; - Console.WriteLine($"{label,20} {data2.Length} Byte"); - label = "JsonNet"; - Console.WriteLine($"{label,20} {dataJson.Length} Byte"); - label = "JsonNet+GZip"; - Console.WriteLine($"{label,20} {dataGzipJson.Length} Byte"); - - Console.WriteLine(); - Console.WriteLine(); - } - - private static string ToHumanReadableSize(long size) - { - return ToHumanReadableSize(new long?(size)); - } - - private static string ToHumanReadableSize(long? size) - { - if (size == null) - { - return "NULL"; - } - - double bytes = size.Value; - - if (bytes <= 1024) - { - return bytes.ToString("f2") + " B"; - } - - bytes = bytes / 1024; - if (bytes <= 1024) - { - return bytes.ToString("f2") + " KB"; - } - - bytes = bytes / 1024; - if (bytes <= 1024) - { - return bytes.ToString("f2") + " MB"; - } - - bytes = bytes / 1024; - if (bytes <= 1024) - { - return bytes.ToString("f2") + " GB"; - } - - bytes = bytes / 1024; - if (bytes <= 1024) - { - return bytes.ToString("f2") + " TB"; - } - - bytes = bytes / 1024; - if (bytes <= 1024) - { - return bytes.ToString("f2") + " PB"; - } - - bytes = bytes / 1024; - if (bytes <= 1024) - { - return bytes.ToString("f2") + " EB"; - } - - bytes = bytes / 1024; - return bytes + " ZB"; - } - } - - internal struct Measure : IDisposable - { - private string label; - private Stopwatch sw; - - public Measure(string label) - { - this.label = label; - System.GC.Collect(2, GCCollectionMode.Forced, blocking: true); - this.sw = Stopwatch.StartNew(); - } - - public void Dispose() - { - this.sw.Stop(); - Console.WriteLine($"{this.label,20} {this.sw.Elapsed.TotalMilliseconds} ms"); - - System.GC.Collect(2, GCCollectionMode.Forced, blocking: true); - } - } - - public class SerializerTarget - { - public int MyProperty1 { get; set; } - - public int MyProperty2 { get; set; } - - public int MyProperty3 { get; set; } - - public int MyProperty4 { get; set; } - - public int MyProperty5 { get; set; } - - public int MyProperty6 { get; set; } - - public int MyProperty7 { get; set; } - - public int MyProperty8 { get; set; } - - public int MyProperty9 { get; set; } - } - - // design concept sketch of Union. - [MessagePack.Union(0, typeof(HogeMoge1))] - [MessagePack.Union(1, typeof(HogeMoge2))] - public interface IHogeMoge - { - } - - public class HogeMoge1 - { - } - - public class HogeMoge2 - { - } - - [MessagePackObject] - public class TestObject - { - [MessagePackObject] - public class PrimitiveObject - { -#pragma warning disable SA1310 // Field names should not contain underscore - [Key(0)] - public int v_int; - - [Key(1)] - public string v_str; - - [Key(2)] - public float v_float; - - [Key(3)] - public bool v_bool; -#pragma warning restore SA1310 // Field names should not contain underscore - - public PrimitiveObject(int vi, string vs, float vf, bool vb) - { - this.v_int = vi; - this.v_str = vs; - this.v_float = vf; - this.v_bool = vb; - } - } - - [Key(0)] - public PrimitiveObject[] objectArray; - - [Key(1)] - public List objectList; - - [Key(2)] - public Dictionary objectMap; - - public void CreateArray(int num) - { - this.objectArray = new PrimitiveObject[num]; - for (int i = 0; i < num; i++) - { - this.objectArray[i] = new PrimitiveObject(i, i.ToString(), (float)i, i % 2 == 0 ? true : false); - } - } - - public void CreateList(int num) - { - this.objectList = new List(num); - for (int i = 0; i < num; i++) - { - this.objectList.Add(new PrimitiveObject(i, i.ToString(), (float)i, i % 2 == 0 ? true : false)); - } - } - - public void CreateMap(int num) - { - this.objectMap = new Dictionary(num); - for (int i = 0; i < num; i++) - { - this.objectMap.Add(i.ToString(), new PrimitiveObject(i, i.ToString(), (float)i, i % 2 == 0 ? true : false)); - } - } - - // I only tested with array - public static TestObject TestBuild() - { - TestObject to = new TestObject(); - to.CreateArray(1000000); - - return to; - } - } - - public class HogeMogeFormatter : IMessagePackFormatter +public class ClassA where T : ClassA.ClassB +{ + public class ClassB { - // Type to Key... - private static readonly Dictionary> Map = new Dictionary> - { - { typeof(HogeMoge1), new KeyValuePair(0, 0) }, - { typeof(HogeMoge2), new KeyValuePair(1, 1) }, - }; - - // If 0~10 don't need it. - private static readonly Dictionary KeyToJumpTable = new Dictionary - { - { 0, 0 }, - { 1, 1 }, - }; - - public void Serialize(ref MessagePackWriter writer, IHogeMoge value, MessagePackSerializerOptions options) - { - KeyValuePair key; - if (Map.TryGetValue(value.GetType(), out key)) - { - writer.WriteArrayHeader(2); - writer.WriteInt32(key.Key); - - switch (key.Value) - { - case 0: - options.Resolver.GetFormatterWithVerify().Serialize(ref writer, (HogeMoge1)value, options); - break; - case 1: - options.Resolver.GetFormatterWithVerify().Serialize(ref writer, (HogeMoge2)value, options); - break; - default: - break; - } - - return; - } - - writer.WriteNil(); - } - - public IHogeMoge Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) - { - // TODO:array header... - var key = reader.ReadInt32(); - - switch (key) - { - case 0: - { - HogeMoge1 result = options.Resolver.GetFormatterWithVerify().Deserialize(ref reader, options); - return (IHogeMoge)result; - } - - case 1: - { - HogeMoge2 result = options.Resolver.GetFormatterWithVerify().Deserialize(ref reader, options); - return (IHogeMoge)result; - } - - default: - { - throw new NotImplementedException(); - } - } - } } } diff --git a/sandbox/Sandbox/Sandbox.csproj b/sandbox/Sandbox/Sandbox.csproj index 5ab57a0ce..f74b899d2 100644 --- a/sandbox/Sandbox/Sandbox.csproj +++ b/sandbox/Sandbox/Sandbox.csproj @@ -1,11 +1,20 @@  + Exe - net6.0 + net9.0 True + + 1591;1701;1702;SA1649;SA1503;SA1402;SA1115 + + + + 1591;1701;1702;SA1649;SA1503;SA1402;SA1115 + + @@ -16,8 +25,8 @@ - + diff --git a/sandbox/Sandbox/codegen.bat b/sandbox/Sandbox/codegen.bat deleted file mode 100644 index 970285c2f..000000000 --- a/sandbox/Sandbox/codegen.bat +++ /dev/null @@ -1,4 +0,0 @@ -@echo off -SETLOCAL -set PS1UnderCmd=1 -powershell.exe -NoProfile -NoLogo -ExecutionPolicy bypass -Command "try { & '%~dpn0.ps1' %*; exit $LASTEXITCODE } catch { write-host $_; exit 1 }" diff --git a/sandbox/Sandbox/codegen.ps1 b/sandbox/Sandbox/codegen.ps1 deleted file mode 100644 index d52201290..000000000 --- a/sandbox/Sandbox/codegen.ps1 +++ /dev/null @@ -1 +0,0 @@ -dotnet run -f net8.0 --project "$PSScriptRoot/../../src/MessagePack.Generator/MessagePack.Generator.csproj" -- -i "$PSScriptRoot/../SharedData/SharedData.csproj" -o "$PSScriptRoot/Generated.cs" diff --git a/src/MessagePack.UnityClient/Assets/Scripts/Tests/Class1.cs b/sandbox/SharedData/Class1.cs similarity index 98% rename from src/MessagePack.UnityClient/Assets/Scripts/Tests/Class1.cs rename to sandbox/SharedData/Class1.cs index 1af2f9e0e..b206c02f5 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/Tests/Class1.cs +++ b/sandbox/SharedData/Class1.cs @@ -100,18 +100,19 @@ public void Serialize(ref MessagePackWriter writer, int value, MessagePackSerial } } - public class OreOreFormatter2 : IMessagePackFormatter + [ExcludeFormatterFromSourceGeneratedResolver] + public class OreOreFormatter2 : IMessagePackFormatter { public OreOreFormatter2(int x, string y) { } - public int Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) + public ulong Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) { throw new NotImplementedException(); } - public void Serialize(ref MessagePackWriter writer, int value, MessagePackSerializerOptions options) + public void Serialize(ref MessagePackWriter writer, ulong value, MessagePackSerializerOptions options) { throw new NotImplementedException(); } @@ -842,6 +843,7 @@ public DynamicArgumentTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 } } + [ExcludeFormatterFromSourceGeneratedResolver] public class DynamicArgumentTupleFormatter : IMessagePackFormatter> { private readonly T1 default1; @@ -1090,7 +1092,7 @@ public class ArrayTestTest } [MessagePackObject(true)] -public class ComplexModel +public partial class ComplexModel { public IDictionary AdditionalProperty { get; private set; } diff --git a/sandbox/SharedData/GeneratedMessagePackResolver.cs b/sandbox/SharedData/GeneratedMessagePackResolver.cs new file mode 100644 index 000000000..cea1a3a1b --- /dev/null +++ b/sandbox/SharedData/GeneratedMessagePackResolver.cs @@ -0,0 +1,11 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using MessagePack; + +namespace SharedData; + +[GeneratedMessagePackResolver] +public partial class GeneratedMessagePackResolver +{ +} diff --git a/sandbox/SharedData/SharedData.csproj b/sandbox/SharedData/SharedData.csproj index f0264075c..a89456256 100644 --- a/sandbox/SharedData/SharedData.csproj +++ b/sandbox/SharedData/SharedData.csproj @@ -1,13 +1,11 @@  + + netstandard2.0 + true + ..\..\opensource.snk - - - - - - - + diff --git a/sandbox/TestData2/Generated.cs b/sandbox/TestData2/Generated.cs deleted file mode 100644 index ac86ae02f..000000000 --- a/sandbox/TestData2/Generated.cs +++ /dev/null @@ -1,1012 +0,0 @@ -// -// THIS (.cs) FILE IS GENERATED BY MPC(MessagePack-CSharp). DO NOT CHANGE IT. -// - -#pragma warning disable 618 -#pragma warning disable 612 -#pragma warning disable 414 -#pragma warning disable 168 -#pragma warning disable CS1591 // document public APIs - -#pragma warning disable SA1312 // Variable names should begin with lower-case letter -#pragma warning disable SA1649 // File name should match first type name - -namespace MessagePack.Resolvers -{ - public class GeneratedResolver : global::MessagePack.IFormatterResolver - { - public static readonly global::MessagePack.IFormatterResolver Instance = new GeneratedResolver(); - - private GeneratedResolver() - { - } - - public global::MessagePack.Formatters.IMessagePackFormatter GetFormatter() - { - return FormatterCache.Formatter; - } - - private static class FormatterCache - { - internal static readonly global::MessagePack.Formatters.IMessagePackFormatter Formatter; - - static FormatterCache() - { - var f = GeneratedResolverGetFormatterHelper.GetFormatter(typeof(T)); - if (f != null) - { - Formatter = (global::MessagePack.Formatters.IMessagePackFormatter)f; - } - } - } - } - - internal static class GeneratedResolverGetFormatterHelper - { - private static readonly global::System.Collections.Generic.Dictionary lookup; - - static GeneratedResolverGetFormatterHelper() - { - lookup = new global::System.Collections.Generic.Dictionary(20) - { - { typeof(global::System.Collections.Generic.List), 0 }, - { typeof(global::System.Collections.Generic.List), 1 }, - { typeof(global::System.Collections.Generic.List), 2 }, - { typeof(global::System.Collections.Generic.List>), 3 }, - { typeof(global::TestData2.NestedGenericTestA), 4 }, - { typeof(global::TestData2.NestedGenericTestB), 5 }, - { typeof(global::TestData2.Nest1.Id), 6 }, - { typeof(global::TestData2.Nest2.Id), 7 }, - { typeof(global::TestData2.A), 8 }, - { typeof(global::TestData2.B), 9 }, - { typeof(global::TestData2.C), 10 }, - { typeof(global::TestData2.Nest1), 11 }, - { typeof(global::TestData2.Nest1.IdType), 12 }, - { typeof(global::TestData2.Nest2), 13 }, - { typeof(global::TestData2.Nest2.IdType), 14 }, - { typeof(global::TestData2.NestedGenericTestC), 15 }, - { typeof(global::TestData2.NullableTest), 16 }, - { typeof(global::TestData2.PropNameCheck1), 17 }, - { typeof(global::TestData2.PropNameCheck2), 18 }, - { typeof(global::TestData2.Record), 19 }, - }; - } - - internal static object GetFormatter(global::System.Type t) - { - int key; - if (!lookup.TryGetValue(t, out key)) - { - return null; - } - - switch (key) - { - case 0: return new global::MessagePack.Formatters.ListFormatter(); - case 1: return new global::MessagePack.Formatters.ListFormatter(); - case 2: return new global::MessagePack.Formatters.ListFormatter(); - case 3: return new global::MessagePack.Formatters.ListFormatter>(); - case 4: return new MessagePack.Formatters.TestData2.NestedGenericTestAFormatter(); - case 5: return new MessagePack.Formatters.TestData2.NestedGenericTestBFormatter(); - case 6: return new MessagePack.Formatters.TestData2.Nest1_IdFormatter(); - case 7: return new MessagePack.Formatters.TestData2.Nest2_IdFormatter(); - case 8: return new MessagePack.Formatters.TestData2.AFormatter(); - case 9: return new MessagePack.Formatters.TestData2.BFormatter(); - case 10: return new MessagePack.Formatters.TestData2.CFormatter(); - case 11: return new MessagePack.Formatters.TestData2.Nest1Formatter(); - case 12: return new MessagePack.Formatters.TestData2.Nest1_IdTypeFormatter(); - case 13: return new MessagePack.Formatters.TestData2.Nest2Formatter(); - case 14: return new MessagePack.Formatters.TestData2.Nest2_IdTypeFormatter(); - case 15: return new MessagePack.Formatters.TestData2.NestedGenericTestCFormatter(); - case 16: return new MessagePack.Formatters.TestData2.NullableTestFormatter(); - case 17: return new MessagePack.Formatters.TestData2.PropNameCheck1Formatter(); - case 18: return new MessagePack.Formatters.TestData2.PropNameCheck2Formatter(); - case 19: return new MessagePack.Formatters.TestData2.RecordFormatter(); - default: return null; - } - } - } -} - -#pragma warning restore 168 -#pragma warning restore 414 -#pragma warning restore 618 -#pragma warning restore 612 - -#pragma warning restore SA1312 // Variable names should begin with lower-case letter -#pragma warning restore SA1649 // File name should match first type name - - -// -// THIS (.cs) FILE IS GENERATED BY MPC(MessagePack-CSharp). DO NOT CHANGE IT. -// - -#pragma warning disable 618 -#pragma warning disable 612 -#pragma warning disable 414 -#pragma warning disable 168 -#pragma warning disable CS1591 // document public APIs - -#pragma warning disable SA1403 // File may only contain a single namespace -#pragma warning disable SA1649 // File name should match first type name - -namespace MessagePack.Formatters.TestData2 -{ - - public sealed class Nest1_IdFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::TestData2.Nest1.Id value, global::MessagePack.MessagePackSerializerOptions options) - { - writer.Write((global::System.Int32)value); - } - - public global::TestData2.Nest1.Id Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - return (global::TestData2.Nest1.Id)reader.ReadInt32(); - } - } - - public sealed class Nest2_IdFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::TestData2.Nest2.Id value, global::MessagePack.MessagePackSerializerOptions options) - { - writer.Write((global::System.Int32)value); - } - - public global::TestData2.Nest2.Id Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - return (global::TestData2.Nest2.Id)reader.ReadInt32(); - } - } -} - -#pragma warning restore 168 -#pragma warning restore 414 -#pragma warning restore 618 -#pragma warning restore 612 - -#pragma warning restore SA1403 // File may only contain a single namespace -#pragma warning restore SA1649 // File name should match first type name - - - -// -// THIS (.cs) FILE IS GENERATED BY MPC(MessagePack-CSharp). DO NOT CHANGE IT. -// - -#pragma warning disable 618 -#pragma warning disable 612 -#pragma warning disable 414 -#pragma warning disable 168 -#pragma warning disable CS1591 // document public APIs - -#pragma warning disable SA1129 // Do not use default value type constructor -#pragma warning disable SA1309 // Field names should not begin with underscore -#pragma warning disable SA1312 // Variable names should begin with lower-case letter -#pragma warning disable SA1403 // File may only contain a single namespace -#pragma warning disable SA1649 // File name should match first type name - -namespace MessagePack.Formatters.TestData2 -{ - public sealed class AFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - // a - private static global::System.ReadOnlySpan GetSpan_a() => new byte[1 + 1] { 161, 97 }; - // bs - private static global::System.ReadOnlySpan GetSpan_bs() => new byte[1 + 2] { 162, 98, 115 }; - // c - private static global::System.ReadOnlySpan GetSpan_c() => new byte[1 + 1] { 161, 99 }; - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::TestData2.A value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value is null) - { - writer.WriteNil(); - return; - } - - var formatterResolver = options.Resolver; - writer.WriteMapHeader(3); - writer.WriteRaw(GetSpan_a()); - writer.Write(value.a); - writer.WriteRaw(GetSpan_bs()); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify>(formatterResolver).Serialize(ref writer, value.bs, options); - writer.WriteRaw(GetSpan_c()); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.c, options); - } - - public global::TestData2.A Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var formatterResolver = options.Resolver; - var length = reader.ReadMapHeader(); - var ____result = new global::TestData2.A(); - - for (int i = 0; i < length; i++) - { - var stringKey = global::MessagePack.Internal.CodeGenHelpers.ReadStringSpan(ref reader); - switch (stringKey.Length) - { - default: - FAIL: - reader.Skip(); - continue; - case 1: - switch (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey)) - { - default: goto FAIL; - case 97UL: - ____result.a = reader.ReadInt32(); - continue; - case 99UL: - ____result.c = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - continue; - } - case 2: - if (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey) != 29538UL) { goto FAIL; } - - ____result.bs = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify>(formatterResolver).Deserialize(ref reader, options); - continue; - - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class BFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - // ass - private static global::System.ReadOnlySpan GetSpan_ass() => new byte[1 + 3] { 163, 97, 115, 115 }; - // c - private static global::System.ReadOnlySpan GetSpan_c() => new byte[1 + 1] { 161, 99 }; - // a - private static global::System.ReadOnlySpan GetSpan_a() => new byte[1 + 1] { 161, 97 }; - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::TestData2.B value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value is null) - { - writer.WriteNil(); - return; - } - - var formatterResolver = options.Resolver; - writer.WriteMapHeader(3); - writer.WriteRaw(GetSpan_ass()); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify>(formatterResolver).Serialize(ref writer, value.ass, options); - writer.WriteRaw(GetSpan_c()); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.c, options); - writer.WriteRaw(GetSpan_a()); - writer.Write(value.a); - } - - public global::TestData2.B Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var formatterResolver = options.Resolver; - var length = reader.ReadMapHeader(); - var ____result = new global::TestData2.B(); - - for (int i = 0; i < length; i++) - { - var stringKey = global::MessagePack.Internal.CodeGenHelpers.ReadStringSpan(ref reader); - switch (stringKey.Length) - { - default: - FAIL: - reader.Skip(); - continue; - case 3: - if (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey) != 7566177UL) { goto FAIL; } - - ____result.ass = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify>(formatterResolver).Deserialize(ref reader, options); - continue; - case 1: - switch (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey)) - { - default: goto FAIL; - case 99UL: - ____result.c = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - continue; - case 97UL: - ____result.a = reader.ReadInt32(); - continue; - } - - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class CFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - // b - private static global::System.ReadOnlySpan GetSpan_b() => new byte[1 + 1] { 161, 98 }; - // a - private static global::System.ReadOnlySpan GetSpan_a() => new byte[1 + 1] { 161, 97 }; - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::TestData2.C value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value is null) - { - writer.WriteNil(); - return; - } - - var formatterResolver = options.Resolver; - writer.WriteMapHeader(2); - writer.WriteRaw(GetSpan_b()); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.b, options); - writer.WriteRaw(GetSpan_a()); - writer.Write(value.a); - } - - public global::TestData2.C Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var formatterResolver = options.Resolver; - var length = reader.ReadMapHeader(); - var ____result = new global::TestData2.C(); - - for (int i = 0; i < length; i++) - { - var stringKey = global::MessagePack.Internal.CodeGenHelpers.ReadStringSpan(ref reader); - switch (stringKey.Length) - { - default: - FAIL: - reader.Skip(); - continue; - case 1: - switch (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey)) - { - default: goto FAIL; - case 98UL: - ____result.b = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - continue; - case 97UL: - ____result.a = reader.ReadInt32(); - continue; - } - - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class Nest1Formatter : global::MessagePack.Formatters.IMessagePackFormatter - { - // EnumId - private static global::System.ReadOnlySpan GetSpan_EnumId() => new byte[1 + 6] { 166, 69, 110, 117, 109, 73, 100 }; - // ClassId - private static global::System.ReadOnlySpan GetSpan_ClassId() => new byte[1 + 7] { 167, 67, 108, 97, 115, 115, 73, 100 }; - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::TestData2.Nest1 value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value is null) - { - writer.WriteNil(); - return; - } - - var formatterResolver = options.Resolver; - writer.WriteMapHeader(2); - writer.WriteRaw(GetSpan_EnumId()); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.EnumId, options); - writer.WriteRaw(GetSpan_ClassId()); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.ClassId, options); - } - - public global::TestData2.Nest1 Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var formatterResolver = options.Resolver; - var length = reader.ReadMapHeader(); - var ____result = new global::TestData2.Nest1(); - - for (int i = 0; i < length; i++) - { - var stringKey = global::MessagePack.Internal.CodeGenHelpers.ReadStringSpan(ref reader); - switch (stringKey.Length) - { - default: - FAIL: - reader.Skip(); - continue; - case 6: - if (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey) != 110266531802693UL) { goto FAIL; } - - ____result.EnumId = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - continue; - case 7: - if (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey) != 28228257876896835UL) { goto FAIL; } - - ____result.ClassId = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - continue; - - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class Nest1_IdTypeFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::TestData2.Nest1.IdType value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value is null) - { - writer.WriteNil(); - return; - } - - writer.WriteMapHeader(0); - } - - public global::TestData2.Nest1.IdType Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - reader.Skip(); - var ____result = new global::TestData2.Nest1.IdType(); - return ____result; - } - } - - public sealed class Nest2Formatter : global::MessagePack.Formatters.IMessagePackFormatter - { - // EnumId - private static global::System.ReadOnlySpan GetSpan_EnumId() => new byte[1 + 6] { 166, 69, 110, 117, 109, 73, 100 }; - // ClassId - private static global::System.ReadOnlySpan GetSpan_ClassId() => new byte[1 + 7] { 167, 67, 108, 97, 115, 115, 73, 100 }; - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::TestData2.Nest2 value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value is null) - { - writer.WriteNil(); - return; - } - - var formatterResolver = options.Resolver; - writer.WriteMapHeader(2); - writer.WriteRaw(GetSpan_EnumId()); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.EnumId, options); - writer.WriteRaw(GetSpan_ClassId()); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.ClassId, options); - } - - public global::TestData2.Nest2 Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var formatterResolver = options.Resolver; - var length = reader.ReadMapHeader(); - var ____result = new global::TestData2.Nest2(); - - for (int i = 0; i < length; i++) - { - var stringKey = global::MessagePack.Internal.CodeGenHelpers.ReadStringSpan(ref reader); - switch (stringKey.Length) - { - default: - FAIL: - reader.Skip(); - continue; - case 6: - if (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey) != 110266531802693UL) { goto FAIL; } - - ____result.EnumId = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - continue; - case 7: - if (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey) != 28228257876896835UL) { goto FAIL; } - - ____result.ClassId = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - continue; - - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class Nest2_IdTypeFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::TestData2.Nest2.IdType value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value is null) - { - writer.WriteNil(); - return; - } - - writer.WriteMapHeader(0); - } - - public global::TestData2.Nest2.IdType Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - reader.Skip(); - var ____result = new global::TestData2.Nest2.IdType(); - return ____result; - } - } - - public sealed class NestedGenericTestAFormatter : global::MessagePack.Formatters.IMessagePackFormatter> - { - // Field - private static global::System.ReadOnlySpan GetSpan_Field() => new byte[1 + 5] { 165, 70, 105, 101, 108, 100 }; - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::TestData2.NestedGenericTestA value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value is null) - { - writer.WriteNil(); - return; - } - - var formatterResolver = options.Resolver; - writer.WriteMapHeader(1); - writer.WriteRaw(GetSpan_Field()); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.Field, options); - } - - public global::TestData2.NestedGenericTestA Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var formatterResolver = options.Resolver; - var length = reader.ReadMapHeader(); - var ____result = new global::TestData2.NestedGenericTestA(); - - for (int i = 0; i < length; i++) - { - var stringKey = global::MessagePack.Internal.CodeGenHelpers.ReadStringSpan(ref reader); - switch (stringKey.Length) - { - default: - FAIL: - reader.Skip(); - continue; - case 5: - if (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey) != 431315315014UL) { goto FAIL; } - - ____result.Field = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - continue; - - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class NestedGenericTestBFormatter : global::MessagePack.Formatters.IMessagePackFormatter> - { - // Field - private static global::System.ReadOnlySpan GetSpan_Field() => new byte[1 + 5] { 165, 70, 105, 101, 108, 100 }; - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::TestData2.NestedGenericTestB value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value is null) - { - writer.WriteNil(); - return; - } - - var formatterResolver = options.Resolver; - writer.WriteMapHeader(1); - writer.WriteRaw(GetSpan_Field()); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify>>(formatterResolver).Serialize(ref writer, value.Field, options); - } - - public global::TestData2.NestedGenericTestB Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var formatterResolver = options.Resolver; - var length = reader.ReadMapHeader(); - var ____result = new global::TestData2.NestedGenericTestB(); - - for (int i = 0; i < length; i++) - { - var stringKey = global::MessagePack.Internal.CodeGenHelpers.ReadStringSpan(ref reader); - switch (stringKey.Length) - { - default: - FAIL: - reader.Skip(); - continue; - case 5: - if (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey) != 431315315014UL) { goto FAIL; } - - ____result.Field = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify>>(formatterResolver).Deserialize(ref reader, options); - continue; - - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class NestedGenericTestCFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - // Field - private static global::System.ReadOnlySpan GetSpan_Field() => new byte[1 + 5] { 165, 70, 105, 101, 108, 100 }; - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::TestData2.NestedGenericTestC value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value is null) - { - writer.WriteNil(); - return; - } - - var formatterResolver = options.Resolver; - writer.WriteMapHeader(1); - writer.WriteRaw(GetSpan_Field()); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify>(formatterResolver).Serialize(ref writer, value.Field, options); - } - - public global::TestData2.NestedGenericTestC Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var formatterResolver = options.Resolver; - var length = reader.ReadMapHeader(); - var ____result = new global::TestData2.NestedGenericTestC(); - - for (int i = 0; i < length; i++) - { - var stringKey = global::MessagePack.Internal.CodeGenHelpers.ReadStringSpan(ref reader); - switch (stringKey.Length) - { - default: - FAIL: - reader.Skip(); - continue; - case 5: - if (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey) != 431315315014UL) { goto FAIL; } - - ____result.Field = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify>(formatterResolver).Deserialize(ref reader, options); - continue; - - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class NullableTestFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - // a - private static global::System.ReadOnlySpan GetSpan_a() => new byte[1 + 1] { 161, 97 }; - // b - private static global::System.ReadOnlySpan GetSpan_b() => new byte[1 + 1] { 161, 98 }; - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::TestData2.NullableTest value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value is null) - { - writer.WriteNil(); - return; - } - - var formatterResolver = options.Resolver; - writer.WriteMapHeader(2); - writer.WriteRaw(GetSpan_a()); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.a, options); - writer.WriteRaw(GetSpan_b()); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify>(formatterResolver).Serialize(ref writer, value.b, options); - } - - public global::TestData2.NullableTest Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var formatterResolver = options.Resolver; - var length = reader.ReadMapHeader(); - var ____result = new global::TestData2.NullableTest(); - - for (int i = 0; i < length; i++) - { - var stringKey = global::MessagePack.Internal.CodeGenHelpers.ReadStringSpan(ref reader); - switch (stringKey.Length) - { - default: - FAIL: - reader.Skip(); - continue; - case 1: - switch (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey)) - { - default: goto FAIL; - case 97UL: - ____result.a = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - continue; - case 98UL: - ____result.b = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify>(formatterResolver).Deserialize(ref reader, options); - continue; - } - - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class PropNameCheck1Formatter : global::MessagePack.Formatters.IMessagePackFormatter - { - // MyProperty1 - private static global::System.ReadOnlySpan GetSpan_MyProperty1() => new byte[1 + 11] { 171, 77, 121, 80, 114, 111, 112, 101, 114, 116, 121, 49 }; - // MyProperty2 - private static global::System.ReadOnlySpan GetSpan_MyProperty2() => new byte[1 + 11] { 171, 77, 121, 80, 114, 111, 112, 101, 114, 116, 121, 50 }; - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::TestData2.PropNameCheck1 value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value is null) - { - writer.WriteNil(); - return; - } - - var formatterResolver = options.Resolver; - writer.WriteMapHeader(2); - writer.WriteRaw(GetSpan_MyProperty1()); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.MyProperty1, options); - writer.WriteRaw(GetSpan_MyProperty2()); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.MyProperty2, options); - } - - public global::TestData2.PropNameCheck1 Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var formatterResolver = options.Resolver; - var length = reader.ReadMapHeader(); - var ____result = new global::TestData2.PropNameCheck1(); - - for (int i = 0; i < length; i++) - { - var stringKey = global::MessagePack.Internal.CodeGenHelpers.ReadStringSpan(ref reader); - switch (stringKey.Length) - { - default: - FAIL: - reader.Skip(); - continue; - case 11: - switch (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey)) - { - default: goto FAIL; - case 8243118316933118285UL: - switch (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey)) - { - default: goto FAIL; - case 3242356UL: - ____result.MyProperty1 = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - continue; - case 3307892UL: - ____result.MyProperty2 = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - continue; - } - - } - - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class PropNameCheck2Formatter : global::MessagePack.Formatters.IMessagePackFormatter - { - // MyProperty1 - private static global::System.ReadOnlySpan GetSpan_MyProperty1() => new byte[1 + 11] { 171, 77, 121, 80, 114, 111, 112, 101, 114, 116, 121, 49 }; - // MyProperty2 - private static global::System.ReadOnlySpan GetSpan_MyProperty2() => new byte[1 + 11] { 171, 77, 121, 80, 114, 111, 112, 101, 114, 116, 121, 50 }; - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::TestData2.PropNameCheck2 value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value is null) - { - writer.WriteNil(); - return; - } - - var formatterResolver = options.Resolver; - writer.WriteMapHeader(2); - writer.WriteRaw(GetSpan_MyProperty1()); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.MyProperty1, options); - writer.WriteRaw(GetSpan_MyProperty2()); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.MyProperty2, options); - } - - public global::TestData2.PropNameCheck2 Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var formatterResolver = options.Resolver; - var length = reader.ReadMapHeader(); - var ____result = new global::TestData2.PropNameCheck2(); - - for (int i = 0; i < length; i++) - { - var stringKey = global::MessagePack.Internal.CodeGenHelpers.ReadStringSpan(ref reader); - switch (stringKey.Length) - { - default: - FAIL: - reader.Skip(); - continue; - case 11: - switch (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey)) - { - default: goto FAIL; - case 8243118316933118285UL: - switch (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey)) - { - default: goto FAIL; - case 3242356UL: - ____result.MyProperty1 = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - continue; - case 3307892UL: - ____result.MyProperty2 = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - continue; - } - - } - - } - } - - reader.Depth--; - return ____result; - } - } - - public sealed class RecordFormatter : global::MessagePack.Formatters.IMessagePackFormatter - { - // SomeProperty - private static global::System.ReadOnlySpan GetSpan_SomeProperty() => new byte[1 + 12] { 172, 83, 111, 109, 101, 80, 114, 111, 112, 101, 114, 116, 121 }; - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, global::TestData2.Record value, global::MessagePack.MessagePackSerializerOptions options) - { - if (value is null) - { - writer.WriteNil(); - return; - } - - var formatterResolver = options.Resolver; - writer.WriteMapHeader(1); - writer.WriteRaw(GetSpan_SomeProperty()); - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Serialize(ref writer, value.SomeProperty, options); - } - - public global::TestData2.Record Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - options.Security.DepthStep(ref reader); - var formatterResolver = options.Resolver; - var length = reader.ReadMapHeader(); - var __SomeProperty__ = default(string); - - for (int i = 0; i < length; i++) - { - var stringKey = global::MessagePack.Internal.CodeGenHelpers.ReadStringSpan(ref reader); - switch (stringKey.Length) - { - default: - FAIL: - reader.Skip(); - continue; - case 12: - if (!global::System.MemoryExtensions.SequenceEqual(stringKey, GetSpan_SomeProperty().Slice(1))) { goto FAIL; } - - __SomeProperty__ = global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify(formatterResolver).Deserialize(ref reader, options); - continue; - - } - } - - var ____result = new global::TestData2.Record(__SomeProperty__); - reader.Depth--; - return ____result; - } - } - -} - -#pragma warning restore 168 -#pragma warning restore 414 -#pragma warning restore 618 -#pragma warning restore 612 - -#pragma warning restore SA1129 // Do not use default value type constructor -#pragma warning restore SA1309 // Field names should not begin with underscore -#pragma warning restore SA1312 // Variable names should begin with lower-case letter -#pragma warning restore SA1403 // File may only contain a single namespace -#pragma warning restore SA1649 // File name should match first type name - diff --git a/sandbox/TestData2/TestData2.csproj b/sandbox/TestData2/TestData2.csproj index 70735ba36..6ae41d577 100644 --- a/sandbox/TestData2/TestData2.csproj +++ b/sandbox/TestData2/TestData2.csproj @@ -1,11 +1,12 @@  - net462 + net472 - - + + + diff --git a/sandbox/TestData2/UnionInterface.cs b/sandbox/TestData2/UnionInterface.cs new file mode 100644 index 000000000..c4d6d54e1 --- /dev/null +++ b/sandbox/TestData2/UnionInterface.cs @@ -0,0 +1,22 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma warning disable SA1307 // Accessible fields should begin with upper-case letter +#pragma warning disable SA1401 // Fields should be private +#pragma warning disable SA1402 // File may only contain a single type +#pragma warning disable SA1649 // File name should match first type name + +namespace TestData2 +{ + [Union(0, typeof(UnionInterfaceImplementation))] + public interface IUnionInterface + { + float Value { get; } + } + + [MessagePackObject(true)] + public class UnionInterfaceImplementation : IUnionInterface + { + public float Value { get; set; } + } +} diff --git a/sandbox/TestData2/codegen.bat b/sandbox/TestData2/codegen.bat deleted file mode 100644 index 970285c2f..000000000 --- a/sandbox/TestData2/codegen.bat +++ /dev/null @@ -1,4 +0,0 @@ -@echo off -SETLOCAL -set PS1UnderCmd=1 -powershell.exe -NoProfile -NoLogo -ExecutionPolicy bypass -Command "try { & '%~dpn0.ps1' %*; exit $LASTEXITCODE } catch { write-host $_; exit 1 }" diff --git a/sandbox/TestData2/codegen.ps1 b/sandbox/TestData2/codegen.ps1 deleted file mode 100644 index b964af899..000000000 --- a/sandbox/TestData2/codegen.ps1 +++ /dev/null @@ -1 +0,0 @@ -dotnet run -f net8.0 --project "$PSScriptRoot/../../src/MessagePack.Generator/MessagePack.Generator.csproj" -- -i "$PSScriptRoot/TestData2.csproj" -o "$PSScriptRoot/Generated.cs" diff --git a/sandbox/codegen.ps1 b/sandbox/codegen.ps1 deleted file mode 100644 index f8f1b9245..000000000 --- a/sandbox/codegen.ps1 +++ /dev/null @@ -1,11 +0,0 @@ -$codeGenScripts = - "$PSScriptRoot/Sandbox/codegen.ps1", - "$PSScriptRoot/TestData2/codegen.ps1" - -$exitCode = 0 -$codeGenScripts | % { - & $_ - if ($LASTEXITCODE -ne 0) { $exitCode = 1 } -} - -exit $exitCode diff --git a/settings.VisualStudio.json b/settings.VisualStudio.json new file mode 100644 index 000000000..7abb4a060 --- /dev/null +++ b/settings.VisualStudio.json @@ -0,0 +1,3 @@ +{ + "textEditor.codeCleanup.profile": "profile1" +} diff --git a/src/AssemblyInfo.cs b/src/AssemblyInfo.cs index e083c6a92..3e3050aef 100644 --- a/src/AssemblyInfo.cs +++ b/src/AssemblyInfo.cs @@ -1,6 +1,7 @@ // Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; [assembly: DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] diff --git a/src/AssemblyInfo.vb b/src/AssemblyInfo.vb new file mode 100644 index 000000000..319edd394 --- /dev/null +++ b/src/AssemblyInfo.vb @@ -0,0 +1,6 @@ +' Copyright (c) All contributors. All rights reserved. +' Licensed under the MIT license. See LICENSE file in the project root for full license information. + +Imports System.Runtime.InteropServices + + diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 199d8a013..1cde74815 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,8 +1,19 @@ + enable True + 13 $(MSBuildThisFileDirectory)..\opensource.snk + true + + + + README.md + + + + diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index 9af35cb00..3c038dbe2 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -1,10 +1,16 @@ + - + + + + + + diff --git a/src/MessagePack.Analyzers.CodeFixes/CodeFixes/FormatterCodeFixProvider.cs b/src/MessagePack.Analyzers.CodeFixes/CodeFixes/FormatterCodeFixProvider.cs new file mode 100644 index 000000000..a3cbee71f --- /dev/null +++ b/src/MessagePack.Analyzers.CodeFixes/CodeFixes/FormatterCodeFixProvider.cs @@ -0,0 +1,225 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Diagnostics.CodeAnalysis; + +namespace MessagePack.Analyzers.CodeFixes; + +[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SimpleBaseTypeSyntax)), Shared] +public class FormatterCodeFixProvider : CodeFixProvider +{ + private static readonly ImmutableArray FixableIds = ImmutableArray.Create( + MsgPack00xMessagePackAnalyzer.InaccessibleFormatterTypeId, + MsgPack00xMessagePackAnalyzer.InaccessibleFormatterInstanceId, + MsgPack00xMessagePackAnalyzer.PartialTypeRequiredId, + MsgPack00xMessagePackAnalyzer.NullableReferenceTypeFormatterId); + + public sealed override ImmutableArray FixableDiagnosticIds => FixableIds; + + public sealed override FixAllProvider GetFixAllProvider() + { + return WellKnownFixAllProviders.BatchFixer; + } + + public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + foreach (Diagnostic diagnostic in context.Diagnostics) + { + SyntaxNode? syntaxRoot = await context.Document.GetSyntaxRootAsync(context.CancellationToken); + CSharpSyntaxNode? diagnosticTargetNode = syntaxRoot?.FindNode(diagnostic.Location.SourceSpan) as CSharpSyntaxNode; + BaseTypeDeclarationSyntax? typeDecl = diagnosticTargetNode as BaseTypeDeclarationSyntax; + if (syntaxRoot is not null) + { + switch (diagnostic.Id) + { + case MsgPack00xMessagePackAnalyzer.PartialTypeRequiredId when typeDecl is not null: + context.RegisterCodeFix( + CodeAction.Create( + "Add partial modifier", + ct => AddPartialModifierAsync(context.Document, syntaxRoot, typeDecl, diagnostic, ct), + "AddPartialModifier"), + diagnostic); + + break; + case MsgPack00xMessagePackAnalyzer.InaccessibleFormatterTypeId when typeDecl is not null: + context.RegisterCodeFix( + CodeAction.Create( + "Make type internal", + ct => ExposeMemberInternally(context.Document, syntaxRoot, typeDecl, diagnostic, ct), + "MakeTypeInternal"), + diagnostic); + + break; + case MsgPack00xMessagePackAnalyzer.InaccessibleFormatterInstanceId when typeDecl is not null: + SemanticModel? semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken); + if (semanticModel?.GetDeclaredSymbol(typeDecl, context.CancellationToken) is INamedTypeSymbol typeSymbol) + { + IMethodSymbol? defaultCtor = typeSymbol.InstanceConstructors.FirstOrDefault(c => c.Parameters.Length == 0); + if (defaultCtor is { DeclaringSyntaxReferences: { Length: > 0 } }) + { + MemberDeclarationSyntax? ctorSyntax = defaultCtor.DeclaringSyntaxReferences[0].GetSyntax(context.CancellationToken) as MemberDeclarationSyntax; + if (ctorSyntax is not null) + { + syntaxRoot = await defaultCtor.DeclaringSyntaxReferences[0].SyntaxTree.GetRootAsync(context.CancellationToken); + context.RegisterCodeFix( + CodeAction.Create( + "Make constructor internal", + ct => ExposeMemberInternally(context.Document, syntaxRoot, ctorSyntax, diagnostic, context.CancellationToken), + "MakeCtorInternal"), + diagnostic); + } + } + } + + break; + case MsgPack00xMessagePackAnalyzer.NullableReferenceTypeFormatterId: + if (diagnosticTargetNode is TypeSyntax typeArg) + { + context.RegisterCodeFix( + CodeAction.Create( + "Add nullable annotation", + ct => AddNullableAnnotationAsync(context, typeArg, ct), + "NullableRefAddition"), + diagnostic); + } + + break; + } + } + } + } + + private static async Task AddNullableAnnotationAsync(CodeFixContext context, TypeSyntax typeArg, CancellationToken ct) + { + SyntaxNode? root = await context.Document.GetSyntaxRootAsync(ct); + if (root is null) + { + return context.Document; + } + + // Attempt to update any and all references to this type. + BaseTypeDeclarationSyntax? typeDecl = typeArg.FirstAncestorOrSelf(); + if (typeDecl is not null) + { + SemanticModel? semanticModel = await context.Document.GetSemanticModelAsync(ct); + if (semanticModel is not null) + { + ITypeSymbol? nonAnnotated = semanticModel.GetTypeInfo(typeArg, ct).Type; + if (nonAnnotated is not null) + { + BaseTypeDeclarationSyntax updatedTypeDecl = typeDecl.ReplaceNodes( + typeDecl.DescendantNodes().OfType(), + (old, current) => + { + ITypeSymbol? actual = semanticModel.GetTypeInfo(old, ct).Type; + return SymbolEqualityComparer.IncludeNullability.Equals(actual, nonAnnotated) + ? SyntaxFactory.NullableType(current.WithoutTrailingTrivia()).WithTrailingTrivia(current.GetTrailingTrivia()) + : current; + }); + root = root.ReplaceNode(typeDecl, updatedTypeDecl); + } + } + } + + Document document = context.Document; + document = document.WithSyntaxRoot(root); + return document; + } + + private static Task AddPartialModifierAsync(Document document, SyntaxNode syntaxRoot, BaseTypeDeclarationSyntax typeDecl, Diagnostic diagnostic, CancellationToken cancellationToken) + { + SyntaxNode? modifiedSyntax = syntaxRoot; + if (TryAddPartialModifier(typeDecl, out BaseTypeDeclarationSyntax? modified)) + { + modifiedSyntax = modifiedSyntax.ReplaceNode(typeDecl, modified); + } + + foreach (Location addlLocation in diagnostic.AdditionalLocations) + { + BaseTypeDeclarationSyntax? addlType = modifiedSyntax.FindNode(addlLocation.SourceSpan) as BaseTypeDeclarationSyntax; + if (addlType is not null && TryAddPartialModifier(addlType, out modified)) + { + modifiedSyntax = modifiedSyntax.ReplaceNode(addlType, modified); + } + } + + document = document.WithSyntaxRoot(modifiedSyntax); + return Task.FromResult(document); + } + + private static Task ExposeMemberInternally(Document document, SyntaxNode syntaxRoot, MemberDeclarationSyntax memberDecl, Diagnostic diagnostic, CancellationToken ct) + { + SyntaxNode? modifiedSyntax = syntaxRoot.ReplaceNode(memberDecl, memberDecl.WithModifiers(AddInternalVisibility(memberDecl.Modifiers))); + document = document.WithSyntaxRoot(modifiedSyntax); + return Task.FromResult(document); + } + + private static SyntaxTokenList AddInternalVisibility(SyntaxTokenList modifiers) + { + SyntaxToken internalKeywordToken = SyntaxFactory.Token(SyntaxKind.InternalKeyword); + + int privateIndex = -1, protectedIndex = -1, internalIndex = -1, publicIndex = -1; + for (int i = 0; i < modifiers.Count; i++) + { + switch (modifiers[i].Kind()) + { + case SyntaxKind.PrivateKeyword: + privateIndex = i; + break; + case SyntaxKind.ProtectedKeyword: + protectedIndex = i; + break; + case SyntaxKind.InternalKeyword: + internalIndex = i; + break; + case SyntaxKind.PublicKeyword: + publicIndex = i; + break; + } + } + + if (internalIndex != -1) + { + // Nothing to do. + return modifiers; + } + + if (privateIndex != -1 && protectedIndex != -1) + { + // Upgrade private protected to internal. + SyntaxTokenList newModifiers = privateIndex < protectedIndex + ? modifiers.RemoveAt(protectedIndex).RemoveAt(privateIndex) + : modifiers.RemoveAt(privateIndex).RemoveAt(protectedIndex); + return newModifiers.Insert(0, internalKeywordToken); + } + + if (protectedIndex != -1) + { + // upgrade to protected internal + return modifiers.Insert(protectedIndex + 1, internalKeywordToken); + } + + if (privateIndex != -1) + { + return ReplaceModifierInList(privateIndex); + } + + // No visibility keywords exist. Add "internal". + return modifiers.Insert(0, internalKeywordToken); + + SyntaxToken ReplaceModifier(SyntaxToken original) => internalKeywordToken.WithLeadingTrivia(original.LeadingTrivia).WithTrailingTrivia(original.TrailingTrivia); + SyntaxTokenList ReplaceModifierInList(int index) => modifiers.Replace(modifiers[index], ReplaceModifier(modifiers[index])); + } + + private static bool TryAddPartialModifier(BaseTypeDeclarationSyntax typeDeclaration, [NotNullWhen(true)] out BaseTypeDeclarationSyntax? modified) + { + if (typeDeclaration.Modifiers.Any(SyntaxKind.PartialKeyword)) + { + modified = null; + return false; + } + + modified = typeDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.PartialKeyword)); + return true; + } +} diff --git a/src/MessagePack.Analyzers.CodeFixes/CodeFixes/MessagePackCodeFixProvider.cs b/src/MessagePack.Analyzers.CodeFixes/CodeFixes/MessagePackCodeFixProvider.cs new file mode 100644 index 000000000..9c380ee8f --- /dev/null +++ b/src/MessagePack.Analyzers.CodeFixes/CodeFixes/MessagePackCodeFixProvider.cs @@ -0,0 +1,236 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace MessagePack.Analyzers.CodeFixes; + +[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(MessagePackCodeFixProvider)), Shared] +public class MessagePackCodeFixProvider : CodeFixProvider +{ + public const string AddKeyAttributeEquivanceKey = "MessagePackAnalyzer.AddKeyAttribute"; + public const string AddIgnoreMemberAttributeEquivalenceKey = "MessagePackAnalyzer.AddIgnoreMemberAttribute"; + + public override ImmutableArray FixableDiagnosticIds + { + get + { + return ImmutableArray.Create( + MsgPack00xMessagePackAnalyzer.MemberNeedsKey.Id, + MsgPack00xMessagePackAnalyzer.TypeMustBeMessagePackObject.Id); + } + } + + public override FixAllProvider GetFixAllProvider() + { + return WellKnownFixAllProviders.BatchFixer; + } + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false) as CompilationUnitSyntax; + if (root is null) + { + return; + } + + var model = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + if (model is null) + { + return; + } + + var targetNode = root.FindNode(context.Span); + var myTypeInfo = model.GetTypeInfo(targetNode, context.CancellationToken); + + var typeName = context.Diagnostics[0]?.Properties.GetValueOrDefault("type", null); + var namedSymbol = + myTypeInfo.Type as INamedTypeSymbol ?? + (typeName is not null ? model.Compilation.GetTypeByMetadataName(typeName.Replace("global::", string.Empty)) : null); + + if (namedSymbol is null) + { + var property = targetNode as PropertyDeclarationSyntax; + var field = targetNode as FieldDeclarationSyntax; + var dec = targetNode as VariableDeclaratorSyntax; + var identifierName = targetNode as IdentifierNameSyntax; + + ITypeSymbol? targetType = null; + if (property == null && field == null) + { + var typeDeclare = targetNode as TypeDeclarationSyntax; + if (typeDeclare != null) + { + targetType = model.GetDeclaredSymbol(typeDeclare); + } + else if (dec != null) + { + var fieldOrProperty = model.GetDeclaredSymbol(dec) as ISymbol; + if (context.Diagnostics[0].Id == MsgPack00xMessagePackAnalyzer.TypeMustBeMessagePackObject.Id) + { + targetType = (fieldOrProperty as IPropertySymbol)?.Type; + if (targetType == null) + { + targetType = (fieldOrProperty as IFieldSymbol)?.Type; + } + } + else + { + targetType = (fieldOrProperty as IPropertySymbol)?.ContainingType; + if (targetType == null) + { + targetType = (fieldOrProperty as IFieldSymbol)?.ContainingType; + } + } + } + else if (targetNode.Parent?.Parent is RecordDeclarationSyntax recordDeclarationSyntax) + { + // Support primary constructors for records (but not for classes, since their semantics differ!). + targetType = model.GetDeclaredSymbol(recordDeclarationSyntax) as ITypeSymbol; + } + } + else + { + if (context.Diagnostics[0].Id == MsgPack00xMessagePackAnalyzer.TypeMustBeMessagePackObject.Id) + { + targetType = property != null + ? (model.GetDeclaredSymbol(property) as IPropertySymbol)?.Type + : (model.GetDeclaredSymbol(field!) as IFieldSymbol)?.Type; + } + else + { + targetType = property != null + ? (model.GetDeclaredSymbol(property) as IPropertySymbol)?.ContainingType + : (model.GetDeclaredSymbol(field!) as IFieldSymbol)?.ContainingType; + } + } + + if (targetType == null) + { + return; + } + + if (targetType.TypeKind == TypeKind.Array) + { + targetType = ((IArrayTypeSymbol)targetType).ElementType; + } + + namedSymbol = targetType as INamedTypeSymbol; + if (namedSymbol == null) + { + return; + } + } + + CodeAction addKeyAction = CodeAction.Create("Add MessagePack KeyAttribute", c => AddKeyAttributeAsync(context.Document, namedSymbol, c), AddKeyAttributeEquivanceKey); + context.RegisterCodeFix(addKeyAction, context.Diagnostics.First()); + + foreach (Diagnostic diagnostic in context.Diagnostics) + { + SyntaxNode? diagnosticTargetNode = root.FindNode(diagnostic.Location.SourceSpan); + if (diagnosticTargetNode?.AncestorsAndSelf().FirstOrDefault(n => n is FieldDeclarationSyntax or PropertyDeclarationSyntax) is MemberDeclarationSyntax fieldOrProperty) + { + context.RegisterCodeFix( + CodeAction.Create( + "Add MessagePack [IgnoreMember]", + ct => AddIgnoreMemberAttributeAsync(context.Document, fieldOrProperty, ct), + AddIgnoreMemberAttributeEquivalenceKey), + diagnostic); + } + } + } + + private static IEnumerable FindMembersMissingAttributes(INamedTypeSymbol type) + { + bool nonPublicMembersNeedAttributes = false; + ISymbol[] candidateMembers = type.GetAllMembers() + .Where(x => x.Kind == SymbolKind.Property || x.Kind == SymbolKind.Field) + .Where(x => !x.IsStatic) + .Where(x => + { + return x switch + { + IPropertySymbol p => p.ExplicitInterfaceImplementations.Length == 0, + IFieldSymbol f => !f.IsImplicitlyDeclared, + _ => throw new NotSupportedException("Unsupported member type."), + }; + }).ToArray(); + + // Do we need to attribute non-public members? We do if the user opted in or if any non-public members carry attributes. + bool? allowPrivateAttribute = (bool?)type.GetAttributes().FindAttributeShortName(MsgPack00xMessagePackAnalyzer.MessagePackObjectAttributeShortName)?.NamedArguments.FirstOrDefault(a => a.Key == MsgPack00xMessagePackAnalyzer.AllowPrivatePropertyName).Value.Value; + nonPublicMembersNeedAttributes |= allowPrivateAttribute is true || candidateMembers.Any(x => HasAnyIgnoreAttribute(x) || HasAnyKeyAttribute(x)); + + return candidateMembers + .Where(x => !HasAnyIgnoreAttribute(x) && (nonPublicMembersNeedAttributes || (x.DeclaredAccessibility & Accessibility.Public) == Accessibility.Public)); + + static bool HasAnyIgnoreAttribute(ISymbol symbol) => symbol.GetAttributes().FindAttributeShortName(MsgPack00xMessagePackAnalyzer.IgnoreShortName) is not null || symbol.GetAttributes().FindAttributeShortName(MsgPack00xMessagePackAnalyzer.IgnoreDataMemberShortName) is not null; + static bool HasAnyKeyAttribute(ISymbol symbol) => symbol.GetAttributes().FindAttributeShortName(MsgPack00xMessagePackAnalyzer.KeyAttributeShortName) is not null || symbol.GetAttributes().FindAttributeShortName(MsgPack00xMessagePackAnalyzer.DataMemberShortName) is not null; + } + + private static async Task AddKeyAttributeAsync(Document document, INamedTypeSymbol type, CancellationToken cancellationToken) + { + var solutionEditor = new SolutionEditor(document.Project.Solution); + + var targets = FindMembersMissingAttributes(type).ToArray(); + + var startOrder = targets + .Select(x => x.GetAttributes().FindAttributeShortName(MsgPack00xMessagePackAnalyzer.KeyAttributeShortName)) + .Where(x => x != null) + .Select(x => x!.ConstructorArguments[0]) + .Where(x => !x.IsNull) + .Where(x => x.Value is int) + .Select(x => (int)x.Value!) + .DefaultIfEmpty(-1) // if empty, start from zero. + .Max() + 1; + + foreach (var member in targets) + { + if (!member.IsImplicitlyDeclared && member.GetAttributes().FindAttributeShortName(MsgPack00xMessagePackAnalyzer.KeyAttributeShortName) is null) + { + var node = await member.DeclaringSyntaxReferences[0].GetSyntaxAsync(cancellationToken).ConfigureAwait(false); + var documentEditor = await solutionEditor.GetDocumentEditorAsync(document.Project.Solution.GetDocumentId(node.SyntaxTree), cancellationToken).ConfigureAwait(false); + var syntaxGenerator = SyntaxGenerator.GetGenerator(documentEditor.OriginalDocument); + + // Preserve comments on fields. + if (node is VariableDeclaratorSyntax) + { + node = node.Parent; + } + + AttributeListSyntax attributeList = (AttributeListSyntax)syntaxGenerator.Attribute("MessagePack.KeyAttribute", syntaxGenerator.LiteralExpression(startOrder++)); + if (node is ParameterSyntax parameter) + { + // The primary constructor requires special target on the attribute list. + attributeList = attributeList.WithTarget( + SyntaxFactory.AttributeTargetSpecifier(SyntaxFactory.Token(SyntaxKind.PropertyKeyword))) + .WithLeadingTrivia(parameter.GetLeadingTrivia()); + ParameterSyntax attributedParameter = parameter.AddAttributeLists(attributeList); + documentEditor.ReplaceNode(parameter, attributedParameter); + } + else + { + documentEditor.AddAttribute(node, attributeList); + } + } + } + + if (type.GetAttributes().FindAttributeShortName(MsgPack00xMessagePackAnalyzer.MessagePackObjectAttributeShortName) == null) + { + var node = await type.DeclaringSyntaxReferences[0].GetSyntaxAsync(cancellationToken).ConfigureAwait(false); + var documentEditor = await solutionEditor.GetDocumentEditorAsync(document.Project.Solution.GetDocumentId(node.SyntaxTree), cancellationToken).ConfigureAwait(false); + var syntaxGenerator = SyntaxGenerator.GetGenerator(documentEditor.OriginalDocument); + documentEditor.AddAttribute(node, syntaxGenerator.Attribute("MessagePack.MessagePackObject")); + } + + return solutionEditor.GetChangedSolution(); + } + + private static async Task AddIgnoreMemberAttributeAsync(Document document, MemberDeclarationSyntax memberDecl, CancellationToken cancellationToken) + { + SolutionEditor solutionEditor = new(document.Project.Solution); + DocumentEditor documentEditor = await solutionEditor.GetDocumentEditorAsync(document.Id, cancellationToken); + SyntaxGenerator syntaxGenerator = SyntaxGenerator.GetGenerator(documentEditor.OriginalDocument); + + documentEditor.AddAttribute(memberDecl, syntaxGenerator.Attribute("MessagePack.IgnoreMemberAttribute")); + + return solutionEditor.GetChangedSolution(); + } +} diff --git a/src/MessagePack.Analyzers.CodeFixes/CodeFixes/MsgPack015CodeFixProvider.cs b/src/MessagePack.Analyzers.CodeFixes/CodeFixes/MsgPack015CodeFixProvider.cs new file mode 100644 index 000000000..031159abe --- /dev/null +++ b/src/MessagePack.Analyzers.CodeFixes/CodeFixes/MsgPack015CodeFixProvider.cs @@ -0,0 +1,61 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace MessagePack.Analyzers.CodeFixes; + +[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(MsgPack015CodeFixProvider)), Shared] +public class MsgPack015CodeFixProvider : CodeFixProvider +{ + public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(MsgPack00xMessagePackAnalyzer.MessagePackObjectAllowPrivateRequired.Id); + + public override FixAllProvider GetFixAllProvider() + { + return WellKnownFixAllProviders.BatchFixer; + } + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + foreach (Diagnostic diagnostic in context.Diagnostics) + { + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false) as CompilationUnitSyntax; + if (root is null) + { + return; + } + + SyntaxNode targetNode = root.FindNode(diagnostic.Location.SourceSpan); + if (targetNode.FirstAncestorOrSelf() is AttributeSyntax attSyntax) + { + context.RegisterCodeFix( + CodeAction.Create( + "Set AllowPrivate = true", + cancellationToken => SetAllowPrivateAsync(context.Document, attSyntax, cancellationToken), + nameof(MessagePackCodeFixProvider)), + diagnostic); + } + } + } + + private async Task SetAllowPrivateAsync(Document document, AttributeSyntax attSyntax, CancellationToken cancellationToken) + { + SyntaxNode? modifiedRoot = await document.GetSyntaxRootAsync(cancellationToken); + if (modifiedRoot is null) + { + return document; + } + + AttributeArgumentSyntax? existingArgument = attSyntax.ArgumentList?.Arguments.FirstOrDefault(x => x.NameEquals?.Name.Identifier.Text == MsgPack00xMessagePackAnalyzer.AllowPrivatePropertyName); + if (existingArgument is null) + { + AttributeArgumentListSyntax? argumentList = attSyntax.ArgumentList ?? SyntaxFactory.AttributeArgumentList(); + argumentList = argumentList.AddArguments(SyntaxFactory.AttributeArgument(SyntaxFactory.NameEquals(MsgPack00xMessagePackAnalyzer.AllowPrivatePropertyName), null, SyntaxFactory.LiteralExpression(SyntaxKind.TrueLiteralExpression))); + modifiedRoot = modifiedRoot.ReplaceNode(attSyntax, attSyntax.WithArgumentList(argumentList)); + } + else + { + modifiedRoot = modifiedRoot.ReplaceNode(existingArgument.Expression, SyntaxFactory.LiteralExpression(SyntaxKind.TrueLiteralExpression)); + } + + return document.WithSyntaxRoot(modifiedRoot); + } +} diff --git a/src/MessagePack.Analyzers.CodeFixes/Directory.Build.props b/src/MessagePack.Analyzers.CodeFixes/Directory.Build.props new file mode 100644 index 000000000..ea193bb7a --- /dev/null +++ b/src/MessagePack.Analyzers.CodeFixes/Directory.Build.props @@ -0,0 +1,6 @@ + + + true + + + diff --git a/src/MessagePack.Analyzers.CodeFixes/MessagePack.Analyzers.CodeFixes.csproj b/src/MessagePack.Analyzers.CodeFixes/MessagePack.Analyzers.CodeFixes.csproj new file mode 100644 index 000000000..d43c3b435 --- /dev/null +++ b/src/MessagePack.Analyzers.CodeFixes/MessagePack.Analyzers.CodeFixes.csproj @@ -0,0 +1,16 @@ + + + netstandard2.0 + MessagePack.Analyzers + false + enable + + $(MicrosoftCodeAnalysisVersion) + + + + + + + + diff --git a/src/MessagePack.Analyzers.CodeFixes/Usings.cs b/src/MessagePack.Analyzers.CodeFixes/Usings.cs new file mode 100644 index 000000000..19bab2b82 --- /dev/null +++ b/src/MessagePack.Analyzers.CodeFixes/Usings.cs @@ -0,0 +1,13 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +global using System.Collections.Immutable; +global using System.Composition; +global using MessagePack.SourceGenerator; +global using MessagePack.SourceGenerator.Analyzers; +global using Microsoft.CodeAnalysis; +global using Microsoft.CodeAnalysis.CodeActions; +global using Microsoft.CodeAnalysis.CodeFixes; +global using Microsoft.CodeAnalysis.CSharp; +global using Microsoft.CodeAnalysis.CSharp.Syntax; +global using Microsoft.CodeAnalysis.Editing; diff --git a/src/MessagePack.Analyzers/Directory.Build.props b/src/MessagePack.Analyzers/Directory.Build.props new file mode 100644 index 000000000..ea193bb7a --- /dev/null +++ b/src/MessagePack.Analyzers/Directory.Build.props @@ -0,0 +1,6 @@ + + + true + + + diff --git a/src/MessagePack.Analyzers/MessagePack.Analyzers.csproj b/src/MessagePack.Analyzers/MessagePack.Analyzers.csproj new file mode 100644 index 000000000..74ab46cb5 --- /dev/null +++ b/src/MessagePack.Analyzers/MessagePack.Analyzers.csproj @@ -0,0 +1,42 @@ + + + + netstandard2.0 + MessagePackAnalyzer + + Analyzers and source generator for MessagePack for C#. Verify rules for [MessagePackObject] and code fix for [Key]. A roslyn source generator for AOT or faster startup of applications that use the MessagePack nuget package. + Codestin Search App + MsgPack;MessagePack;Serialization;Formatter;Analyzer + false + $(TargetsForTfmSpecificContentInPackage);PackBuildOutputs + true + false + true + + + + + + + + + + + + + + + + + + + + $(MicrosoftCodeAnalysisVersion.Substring(0, $(MicrosoftCodeAnalysisVersion.LastIndexOf('.')))) + + + + + + diff --git a/src/MessagePack.Analyzers/build/MessagePackAnalyzer.targets b/src/MessagePack.Analyzers/build/MessagePackAnalyzer.targets new file mode 100644 index 000000000..05afdf84e --- /dev/null +++ b/src/MessagePack.Analyzers/build/MessagePackAnalyzer.targets @@ -0,0 +1,9 @@ + + + + <_ObsoleteAnalyzerJson Include="@(AdditionalFiles)" Condition="'%(FileName)%(Extension)' == 'MessagePackAnalyzer.json'" /> + + + + diff --git a/src/MessagePack.Annotations/AnalyzerAttributes.cs b/src/MessagePack.Annotations/AnalyzerAttributes.cs new file mode 100644 index 000000000..4a5c8888f --- /dev/null +++ b/src/MessagePack.Annotations/AnalyzerAttributes.cs @@ -0,0 +1,72 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma warning disable SA1649 // File name should match first type name +#pragma warning disable SA1402 // File may only contain a single type + +using System; +using System.Diagnostics; + +namespace MessagePack +{ + // TODO: allow these attributes to also appear on the partial resolver class too. + // This isn't an acceptable exclusive option because we want the analyzer to be able to run even when the resolver partial class isn't generated. + + /// + /// Identifies a custom formatter (one that implements one or more IMessagePackFormatter<T> interfaces) + /// that should be considered when checking that types are serializable and/or included in a source-generated resolver. + /// + /// + /// Formatters identified with this attribute will be included in the source-generated resolver. + /// + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module, AllowMultiple = true)] + [Conditional("NEVERDEFINED")] // We only need this attribute for analysis, so we don't want it to be included in the user's built assembly. + public class MessagePackKnownFormatterAttribute : Attribute + { + public MessagePackKnownFormatterAttribute(Type formatterType) + { + this.FormatterType = formatterType; + } + + /// + /// Gets a type that implements one or more IMessagePackFormatter<T> interfaces. + /// + public Type FormatterType { get; } + } + + /// + /// Identifies a type for which a IMessagePackFormatter<T> exists and will be added manually to the IFormatterResolver by the program. + /// + /// + /// This attribute suppresses warnings by the MessagePack analyzer when it encounters references to the specified type within another serializable type. + /// When possible, using the is preferred. + /// + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module, AllowMultiple = true)] + [Conditional("NEVERDEFINED")] // We only need this attribute for analysis, so we don't want it to be included in the user's built assembly. + public class MessagePackAssumedFormattableAttribute : Attribute + { + public MessagePackAssumedFormattableAttribute(Type formattableType) + { + this.FormattableType = formattableType; + } + + /// + /// Gets a type for which an IMessagePackFormatter<T> is known to exist and will be added via a custom resolver by the program at runtime. + /// + public Type FormattableType { get; } + } + + /// + /// Causes the source generated resolver, which typically includes all implementations of IMessagePackFormatter<T>, + /// to exclude this particular formatter. + /// + /// + /// This is useful when the formatter is intended for special case members, + /// which may apply the to select the private formatter. + /// + [AttributeUsage(AttributeTargets.Class)] + [Conditional("NEVERDEFINED")] + public class ExcludeFormatterFromSourceGeneratedResolverAttribute : Attribute + { + } +} diff --git a/src/MessagePack.Annotations/Attributes.cs b/src/MessagePack.Annotations/Attributes.cs new file mode 100644 index 000000000..13e3346f0 --- /dev/null +++ b/src/MessagePack.Annotations/Attributes.cs @@ -0,0 +1,142 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +#pragma warning disable SA1402 // File may only contain a single type +#pragma warning disable SA1649 // File name should match first type name + +namespace MessagePack +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = true)] + public class MessagePackObjectAttribute : Attribute + { + /// + /// Gets a value indicating whether to automatically serialize all internal and public fields and properties using their property name as the key in a map. + /// + public bool KeyAsPropertyName { get; } + + /// + /// Initializes a new instance of the class. + /// + /// + /// to automatically serialize all internal and public fields and properties using their property name as the key in a map; + /// or to use the attribute to specify the key for each field or property. + /// + public MessagePackObjectAttribute(bool keyAsPropertyName = false) + { + this.KeyAsPropertyName = keyAsPropertyName; + } + + /// + /// Gets or sets a value indicating whether the source generator should not + /// generate a formatter for this type at compile-time. + /// + /// + /// By default, source generators will generate a formatter for every type that is annotated with + /// this attribute to improve startup performance. + /// However if this leads to malfunctions during code generation or at runtime, + /// it can be disabled by setting this property to . + /// When no precompiled formatter is found at runtime, the DynamicObjectResolver + /// will generate a formatter at runtime instead. + /// + public bool SuppressSourceGeneration { get; set; } + + /// + /// Gets or sets a value indicating whether generated formatters should allow non-public members to be serialized and deserialized. + /// + /// + /// + /// This is particularly important to set when is set to if non-public members should be included. + /// + /// + /// This property can also ensure non-public members are serialized by the DynamicObjectResolver so that the application doesn't have to use + /// DynamicObjectResolverAllowPrivate explicitly for an object to be properly serialized. + /// + /// + public bool AllowPrivate { get; set; } + } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)] + public class KeyAttribute : Attribute + { + public int? IntKey { get; } + + public string? StringKey { get; } + + public KeyAttribute(int x) + { + this.IntKey = x; + } + + public KeyAttribute(string x) + { + this.StringKey = x ?? throw new ArgumentNullException(nameof(x)); + } + } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)] + public class IgnoreMemberAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + public class UnionAttribute : Attribute + { + /// + /// Gets the distinguishing value that identifies a particular subtype. + /// + public int Key { get; } + + /// + /// Gets the derived or implementing type. + /// + public Type SubType { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The distinguishing value that identifies a particular subtype. + /// The derived or implementing type. + public UnionAttribute(int key, Type subType) + { + this.Key = key; + this.SubType = subType ?? throw new ArgumentNullException(nameof(subType)); + } + + /// + /// Initializes a new instance of the class. + /// + /// The distinguishing value that identifies a particular subtype. + /// The full name (should be assembly qualified) of the derived or implementing type. + public UnionAttribute(int key, string subType) + { + this.Key = key; + this.SubType = Type.GetType(subType, throwOnError: true); + } + } + + [AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = true)] + public class SerializationConstructorAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface | AttributeTargets.Enum | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = false, Inherited = true)] + public class MessagePackFormatterAttribute : Attribute + { + public Type FormatterType { get; } + + public object?[]? Arguments { get; } + + public MessagePackFormatterAttribute(Type formatterType) + { + this.FormatterType = formatterType ?? throw new ArgumentNullException(nameof(formatterType)); + } + + public MessagePackFormatterAttribute(Type formatterType, params object?[]? arguments) + { + this.FormatterType = formatterType ?? throw new ArgumentNullException(nameof(formatterType)); + this.Arguments = arguments; + } + } +} diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Annotations/IMessagePackSerializationCallbackReceiver.cs b/src/MessagePack.Annotations/IMessagePackSerializationCallbackReceiver.cs similarity index 100% rename from src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Annotations/IMessagePackSerializationCallbackReceiver.cs rename to src/MessagePack.Annotations/IMessagePackSerializationCallbackReceiver.cs diff --git a/src/MessagePack.Annotations/MessagePack.Annotations.csproj b/src/MessagePack.Annotations/MessagePack.Annotations.csproj index 48e95271a..dba6f8d6a 100644 --- a/src/MessagePack.Annotations/MessagePack.Annotations.csproj +++ b/src/MessagePack.Annotations/MessagePack.Annotations.csproj @@ -10,8 +10,4 @@ MsgPack;MessagePack;Serialization;Formatter;Serializer;Unity;Xamarin - - - - diff --git a/src/MessagePack.AspNetCoreMvcFormatter/MessagePack.AspNetCoreMvcFormatter.csproj b/src/MessagePack.AspNetCoreMvcFormatter/MessagePack.AspNetCoreMvcFormatter.csproj index 6203d8a90..3945212df 100644 --- a/src/MessagePack.AspNetCoreMvcFormatter/MessagePack.AspNetCoreMvcFormatter.csproj +++ b/src/MessagePack.AspNetCoreMvcFormatter/MessagePack.AspNetCoreMvcFormatter.csproj @@ -1,20 +1,20 @@  - netstandard2.0;net6.0 + netstandard2.0;net8.0;net9.0 Codestin Search App ASP.NET Core MVC Input/Output MessagePack formatter. MsgPack;MessagePack;Serialization;Formatter;Serializer;aspnetcore;aspnetcoremvc - + - + - + diff --git a/src/MessagePack.Experimental/HardwareIntrinsics/Formatters.cs b/src/MessagePack.Experimental/HardwareIntrinsics/Formatters.cs index 34b7a143f..f7db64b85 100644 --- a/src/MessagePack.Experimental/HardwareIntrinsics/Formatters.cs +++ b/src/MessagePack.Experimental/HardwareIntrinsics/Formatters.cs @@ -107,7 +107,7 @@ public unsafe void Serialize(ref MessagePackWriter writer, sbyte[]? value, Messa } } - ProcessEach: +ProcessEach: while (inputIterator != inputEnd) { writer.Write(*inputIterator++); @@ -234,7 +234,7 @@ public unsafe void Serialize(ref MessagePackWriter writer, short[]? value, Messa } } - ProcessEach: +ProcessEach: while (inputIterator != inputEnd) { writer.Write(*inputIterator++); @@ -363,7 +363,7 @@ public unsafe void Serialize(ref MessagePackWriter writer, int[]? value, Message } } - ProcessEach: +ProcessEach: while (inputIterator != inputEnd) { writer.Write(*inputIterator++); @@ -422,7 +422,7 @@ public unsafe void Serialize(ref MessagePackWriter writer, float[]? value, Messa } } - ProcessEach: +ProcessEach: while (inputIterator != inputEnd) { // Encode float as Big Endian @@ -525,7 +525,7 @@ public unsafe void Serialize(ref MessagePackWriter writer, double[]? value, Mess } } - ProcessEach: +ProcessEach: while (inputIterator != inputEnd) { *outputIterator++ = MessagePackCode.Float64; @@ -651,7 +651,7 @@ public void Serialize(ref MessagePackWriter writer, bool[]? value, MessagePackSe } } - ProcessEach: +ProcessEach: while (inputIterator != inputEnd) { *outputIterator++ = *inputIterator++ ? MessagePackCode.True : MessagePackCode.False; @@ -749,7 +749,7 @@ public void Serialize(ref MessagePackWriter writer, bool[]? value, MessagePackSe } } - ProcessEach: +ProcessEach: while (inputIterator != inputEnd) { switch (*inputIterator++) diff --git a/src/MessagePack.Experimental/MessagePack.Experimental.csproj b/src/MessagePack.Experimental/MessagePack.Experimental.csproj index 58d6fb688..34f38f883 100644 --- a/src/MessagePack.Experimental/MessagePack.Experimental.csproj +++ b/src/MessagePack.Experimental/MessagePack.Experimental.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0;net9.0 true Codestin Search App diff --git a/src/MessagePack.Experimental/UnsafeUnmanagedStructFormatter/UnsafeUnmanagedStructFormatter.cs b/src/MessagePack.Experimental/UnsafeUnmanagedStructFormatter/UnsafeUnmanagedStructFormatter.cs index 0388f5026..37a230c46 100644 --- a/src/MessagePack.Experimental/UnsafeUnmanagedStructFormatter/UnsafeUnmanagedStructFormatter.cs +++ b/src/MessagePack.Experimental/UnsafeUnmanagedStructFormatter/UnsafeUnmanagedStructFormatter.cs @@ -45,7 +45,7 @@ public T Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions var sequence = reader.ReadRaw(sizeof(T)); if (sequence.IsSingleSegment) { - return Unsafe.As(ref Unsafe.AsRef(sequence.FirstSpan[0])); + return Unsafe.As(ref Unsafe.AsRef(in sequence.FirstSpan[0])); } T answer; diff --git a/src/MessagePack.Generator/.editorconfig b/src/MessagePack.Generator/.editorconfig deleted file mode 100644 index 6b835fc65..000000000 --- a/src/MessagePack.Generator/.editorconfig +++ /dev/null @@ -1,4 +0,0 @@ -[*.cs] - -# VSTHRD111: Use ConfigureAwait(bool) -dotnet_diagnostic.VSTHRD111.severity = none diff --git a/src/MessagePack.Generator/MessagePack.Generator.csproj b/src/MessagePack.Generator/MessagePack.Generator.csproj deleted file mode 100644 index dda450aa6..000000000 --- a/src/MessagePack.Generator/MessagePack.Generator.csproj +++ /dev/null @@ -1,31 +0,0 @@ - - - - mpc - Exe - net6.0;net8.0 - true - mpc - Major - - - MessagePack.Generator - Codestin Search App - MessagePack standalone code generator. - MsgPack;MessagePack;Serialization;Formatter;Serializer;Unity;Xamarin - - - - - - - - - - - - - - - - diff --git a/src/MessagePack.Generator/MessagepackCompiler.cs b/src/MessagePack.Generator/MessagepackCompiler.cs deleted file mode 100644 index 17e18f107..000000000 --- a/src/MessagePack.Generator/MessagepackCompiler.cs +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) All contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.Loader; -using System.Threading; -using System.Threading.Tasks; -using ConsoleAppFramework; -using Microsoft.Build.Locator; -using Microsoft.Build.Logging; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.MSBuild; -using Microsoft.Extensions.Hosting; - -namespace MessagePack.Generator -{ - public class MessagepackCompiler : ConsoleAppBase - { - private static async Task Main(string[] args) - { - var instance = MSBuildLocator.RegisterDefaults(); - AssemblyLoadContext.Default.Resolving += (assemblyLoadContext, assemblyName) => - { - var path = Path.Combine(instance.MSBuildPath, assemblyName.Name + ".dll"); - if (File.Exists(path)) - { - return assemblyLoadContext.LoadFromAssemblyPath(path); - } - - return null; - }; - - await Host.CreateDefaultBuilder() - .ConfigureLogging(logging => logging.ReplaceToSimpleConsole()) - .RunConsoleAppFrameworkAsync(args); - } - - public async Task RunAsync( - [Option("i", "Input path to MSBuild project file or the directory containing Unity source files.")] string input, - [Option("o", "Output file path(.cs) or directory (multiple generate file).")] string output, - [Option("c", "Conditional compiler symbols, split with ','. Ignored if a project file is specified for input.")] string? conditionalSymbol = null, - [Option("r", "Set resolver name.")] string resolverName = "GeneratedResolver", - [Option("n", "Set namespace root name.")] string @namespace = "MessagePack", - [Option("m", "Force use map mode serialization.")] bool useMapMode = false, - [Option("ms", "Generate #if-- files by symbols, split with ','.")] string? multipleIfDirectiveOutputSymbols = null, - [Option("ei", "Ignore type names.")] string[]? externalIgnoreTypeNames = null) - { - Workspace? workspace = null; - try - { - Compilation compilation; - if (Directory.Exists(input)) - { - string[]? conditionalSymbols = conditionalSymbol?.Split(','); - compilation = await PseudoCompilation.CreateFromDirectoryAsync(input, conditionalSymbols, this.Context.CancellationToken); - } - else - { - (workspace, compilation) = await this.OpenMSBuildProjectAsync(input, this.Context.CancellationToken); - } - - await new MessagePackCompiler.CodeGenerator(x => Console.WriteLine(x), this.Context.CancellationToken) - .GenerateFileAsync( - compilation, - output, - resolverName, - @namespace, - useMapMode, - multipleIfDirectiveOutputSymbols, - externalIgnoreTypeNames).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - await Console.Error.WriteLineAsync("Canceled"); - throw; - } - finally - { - workspace?.Dispose(); - } - } - - private async Task<(Workspace Workspace, Compilation Compilation)> OpenMSBuildProjectAsync(string projectPath, CancellationToken cancellationToken) - { - var workspace = MSBuildWorkspace.Create(); - try - { - var logger = new ConsoleLogger(Microsoft.Build.Framework.LoggerVerbosity.Quiet); - var project = await workspace.OpenProjectAsync(projectPath, logger, null, cancellationToken); - if (workspace.Diagnostics.Any(x => x.Kind is WorkspaceDiagnosticKind.Failure)) - { - throw new InvalidOperationException("One or more errors occured while opening the project:" + Environment.NewLine + - string.Join(Environment.NewLine, workspace.Diagnostics.Select(x => $"========================================{Environment.NewLine}{x.Message}{Environment.NewLine}========================================"))); - } - - var compilation = await project.GetCompilationAsync(cancellationToken); - if (compilation is null) - { - throw new NotSupportedException("The project does not support creating Compilation."); - } - - return (workspace, compilation); - } - catch - { - workspace.Dispose(); - throw; - } - } - } -} diff --git a/src/MessagePack.Generator/PseudoCompilation.cs b/src/MessagePack.Generator/PseudoCompilation.cs deleted file mode 100644 index 80d5a1ed2..000000000 --- a/src/MessagePack.Generator/PseudoCompilation.cs +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright (c) All contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.Serialization; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace MessagePack.Generator -{ - internal static class PseudoCompilation - { - internal static async Task CreateFromDirectoryAsync(string directoryRoot, IEnumerable? preprocessorSymbols, CancellationToken cancellationToken) - { - var parseOption = new CSharpParseOptions(LanguageVersion.Latest, DocumentationMode.Parse, SourceCodeKind.Regular, CleanPreprocessorSymbols(preprocessorSymbols)); - - var syntaxTrees = new List(); - var hasAnnotations = false; - foreach (var file in IterateCsFileWithoutBinObj(directoryRoot)) - { - var text = File.ReadAllText(NormalizeDirectorySeparators(file), Encoding.UTF8); - var syntax = CSharpSyntaxTree.ParseText(text, parseOption); - syntaxTrees.Add(syntax); - if (Path.GetFileNameWithoutExtension(file) == "Attributes") - { - var root = await syntax.GetRootAsync(cancellationToken).ConfigureAwait(false); - if (root.DescendantNodes().OfType().Any(x => x.Identifier.Text == "MessagePackObjectAttribute")) - { - hasAnnotations = true; - } - } - } - - if (!hasAnnotations) - { - syntaxTrees.Add(CSharpSyntaxTree.ParseText(DummyAnnotation, parseOption)); - } - - var metadata = GetStandardReferences().Select(x => MetadataReference.CreateFromFile(x)).ToArray(); - - var compilation = CSharpCompilation.Create( - "CodeGenTemp", - syntaxTrees, - DistinctReference(metadata), - new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true)); - - return compilation; - } - - private static IEnumerable DistinctReference(IEnumerable metadataReferences) - { - var set = new HashSet(); - foreach (var item in metadataReferences) - { - if (item.Display is object && set.Add(Path.GetFileName(item.Display))) - { - yield return item; - } - } - } - - private static List GetStandardReferences() - { - var standardMetadataType = new[] - { - typeof(object), - typeof(Attribute), - typeof(Enumerable), - typeof(Task<>), - typeof(IgnoreDataMemberAttribute), - typeof(System.Collections.Hashtable), - typeof(System.Collections.Generic.List<>), - typeof(System.Collections.Generic.HashSet<>), - typeof(System.Collections.Immutable.IImmutableList<>), - typeof(System.Linq.ILookup<,>), - typeof(System.Tuple<>), - typeof(System.ValueTuple<>), - typeof(System.Collections.Concurrent.ConcurrentDictionary<,>), - typeof(System.Collections.ObjectModel.ObservableCollection<>), - }; - - var metadata = standardMetadataType - .Select(x => x.Assembly.Location) - .Distinct() - .ToList(); - - var dir = new FileInfo(typeof(object).Assembly.Location).Directory ?? throw new NullReferenceException("Assembly location directory not found!"); - { - var path = Path.Combine(dir.FullName, "netstandard.dll"); - if (File.Exists(path)) - { - metadata.Add(path); - } - } - - { - var path = Path.Combine(dir.FullName, "System.Runtime.dll"); - if (File.Exists(path)) - { - metadata.Add(path); - } - } - - return metadata; - } - - private static IEnumerable? CleanPreprocessorSymbols(IEnumerable? preprocessorSymbols) - { - return preprocessorSymbols?.Where(x => !string.IsNullOrWhiteSpace(x)); - } - - private static IEnumerable IterateCsFileWithoutBinObj(string root) - { - foreach (var item in Directory.EnumerateFiles(root, "*.cs", SearchOption.TopDirectoryOnly)) - { - yield return item; - } - - foreach (var dir in Directory.GetDirectories(root, "*", SearchOption.TopDirectoryOnly)) - { - var dirName = new DirectoryInfo(dir).Name; - if (dirName == "bin" || dirName == "obj") - { - continue; - } - - foreach (var item in IterateCsFileWithoutBinObj(dir)) - { - yield return item; - } - } - } - - private static string NormalizeDirectorySeparators(string path) - { - return path.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar); - } - - private const string DummyAnnotation = @" -using System; - -namespace MessagePack -{ - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = true)] - public class MessagePackObjectAttribute : Attribute - { - public bool KeyAsPropertyName { get; private set; } - - public MessagePackObjectAttribute(bool keyAsPropertyName = false) - { - this.KeyAsPropertyName = keyAsPropertyName; - } - } - - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)] - public class KeyAttribute : Attribute - { - public int? IntKey { get; private set; } - public string StringKey { get; private set; } - - public KeyAttribute(int x) - { - this.IntKey = x; - } - - public KeyAttribute(string x) - { - this.StringKey = x; - } - } - - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)] - public class IgnoreMemberAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class, AllowMultiple = true, Inherited = false)] - public class UnionAttribute : Attribute - { - public int Key { get; private set; } - public Type SubType { get; private set; } - - public UnionAttribute(int key, Type subType) - { - this.Key = key; - this.SubType = subType; - } - } - - [AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = true)] - public class SerializationConstructorAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface | AttributeTargets.Enum | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)] - public class MessagePackFormatterAttribute : Attribute - { - public Type FormatterType { get; private set; } - public object[] Arguments { get; private set; } - - public MessagePackFormatterAttribute(Type formatterType) - { - this.FormatterType = formatterType; - } - - public MessagePackFormatterAttribute(Type formatterType, params object[] arguments) - { - this.FormatterType = formatterType; - this.Arguments = arguments; - } - } -} - -using System; -using System.Collections.Generic; -using System.Text; - -namespace MessagePack -{ - public interface IMessagePackSerializationCallbackReceiver - { - void OnBeforeSerialize(); - void OnAfterDeserialize(); - } -} -"; - } -} diff --git a/src/MessagePack.GeneratorCore/.editorconfig b/src/MessagePack.GeneratorCore/.editorconfig deleted file mode 100644 index 6b835fc65..000000000 --- a/src/MessagePack.GeneratorCore/.editorconfig +++ /dev/null @@ -1,4 +0,0 @@ -[*.cs] - -# VSTHRD111: Use ConfigureAwait(bool) -dotnet_diagnostic.VSTHRD111.severity = none diff --git a/src/MessagePack.GeneratorCore/CodeAnalysis/Definitions.cs b/src/MessagePack.GeneratorCore/CodeAnalysis/Definitions.cs deleted file mode 100644 index c7282896d..000000000 --- a/src/MessagePack.GeneratorCore/CodeAnalysis/Definitions.cs +++ /dev/null @@ -1,282 +0,0 @@ -// Copyright (c) All contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Collections.Generic; -using System.Linq; - -#pragma warning disable SA1402 // File may only contain a single type -#pragma warning disable SA1649 // File name should match first type name - -namespace MessagePackCompiler.CodeAnalysis -{ - public interface INamespaceInfo - { - string? Namespace { get; } - } - - public interface IResolverRegisterInfo - { - string FullName { get; } - - string FormatterName { get; } - } - - public class ObjectSerializationInfo : IResolverRegisterInfo, INamespaceInfo - { - public string Name { get; } - - public string FullName { get; } - - public string? Namespace { get; } - - public GenericTypeParameterInfo[] GenericTypeParameters { get; } - - public bool IsOpenGenericType { get; } - - public bool IsIntKey { get; } - - public bool IsStringKey - { - get { return !this.IsIntKey; } - } - - public bool IsClass { get; } - - public MemberSerializationInfo[] ConstructorParameters { get; } - - public MemberSerializationInfo[] Members { get; } - - public bool HasIMessagePackSerializationCallbackReceiver { get; } - - public bool NeedsCastOnBefore { get; } - - public bool NeedsCastOnAfter { get; } - - public string FormatterName => this.Namespace == null ? FormatterNameWithoutNameSpace : this.Namespace + "." + FormatterNameWithoutNameSpace; - - public string FormatterNameWithoutNameSpace => this.Name + "Formatter" + (this.IsOpenGenericType ? $"<{string.Join(", ", this.GenericTypeParameters.Select(x => x.Name))}>" : string.Empty); - - public int WriteCount - { - get - { - if (this.IsStringKey) - { - return this.Members.Count(x => x.IsReadable); - } - else - { - return this.MaxKey; - } - } - } - - public int MaxKey - { - get - { - return this.Members.Where(x => x.IsReadable).Select(x => x.IntKey).DefaultIfEmpty(-1).Max(); - } - } - - public MemberSerializationInfo? GetMember(int index) - { - return this.Members.FirstOrDefault(x => x.IntKey == index); - } - - public string GetConstructorString() - { - var args = string.Join(", ", this.ConstructorParameters.Select(x => "__" + x.Name + "__")); - return $"{this.FullName}({args})"; - } - - public ObjectSerializationInfo(bool isClass, bool isOpenGenericType, GenericTypeParameterInfo[] genericTypeParameterInfos, MemberSerializationInfo[] constructorParameters, bool isIntKey, MemberSerializationInfo[] members, string name, string fullName, string? @namespace, bool hasSerializationConstructor, bool needsCastOnAfter, bool needsCastOnBefore) - { - IsClass = isClass; - IsOpenGenericType = isOpenGenericType; - GenericTypeParameters = genericTypeParameterInfos; - ConstructorParameters = constructorParameters; - IsIntKey = isIntKey; - Members = members; - Name = name; - FullName = fullName; - Namespace = @namespace; - HasIMessagePackSerializationCallbackReceiver = hasSerializationConstructor; - NeedsCastOnAfter = needsCastOnAfter; - NeedsCastOnBefore = needsCastOnBefore; - } - } - - public class GenericTypeParameterInfo - { - public string Name { get; } - - public string Constraints { get; } - - public bool HasConstraints { get; } - - public GenericTypeParameterInfo(string name, string constraints) - { - Name = name ?? throw new ArgumentNullException(nameof(name)); - Constraints = constraints ?? throw new ArgumentNullException(nameof(name)); - HasConstraints = constraints != string.Empty; - } - } - - public class MemberSerializationInfo - { - public bool IsProperty { get; } - - public bool IsWritable { get; } - - public bool IsReadable { get; } - - public int IntKey { get; } - - public string StringKey { get; } - - public string Type { get; } - - public string Name { get; } - - public string ShortTypeName { get; } - - public string? CustomFormatterTypeName { get; } - - private readonly HashSet primitiveTypes = new(Generator.ShouldUseFormatterResolverHelper.PrimitiveTypes); - - public MemberSerializationInfo(bool isProperty, bool isWritable, bool isReadable, int intKey, string stringKey, string name, string type, string shortTypeName, string? customFormatterTypeName) - { - IsProperty = isProperty; - IsWritable = isWritable; - IsReadable = isReadable; - IntKey = intKey; - StringKey = stringKey; - Type = type; - Name = name; - ShortTypeName = shortTypeName; - CustomFormatterTypeName = customFormatterTypeName; - } - - public string GetSerializeMethodString() - { - if (CustomFormatterTypeName != null) - { - return $"this.__{this.Name}CustomFormatter__.Serialize(ref writer, value.{this.Name}, options)"; - } - else if (this.primitiveTypes.Contains(this.Type)) - { - return "writer.Write(value." + this.Name + ")"; - } - else - { - return $"global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify<{this.Type}>(formatterResolver).Serialize(ref writer, value.{this.Name}, options)"; - } - } - - public string GetDeserializeMethodString() - { - if (CustomFormatterTypeName != null) - { - return $"this.__{this.Name}CustomFormatter__.Deserialize(ref reader, options)"; - } - else if (this.primitiveTypes.Contains(this.Type)) - { - if (this.Type == "byte[]") - { - return "global::MessagePack.Internal.CodeGenHelpers.GetArrayFromNullableSequence(reader.ReadBytes())"; - } - else - { - return $"reader.Read{this.ShortTypeName!.Replace("[]", "s")}()"; - } - } - else - { - return $"global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify<{this.Type}>(formatterResolver).Deserialize(ref reader, options)"; - } - } - } - - public class EnumSerializationInfo : IResolverRegisterInfo, INamespaceInfo - { - public EnumSerializationInfo(string? @namespace, string name, string fullName, string underlyingType) - { - Namespace = @namespace; - Name = name; - FullName = fullName; - UnderlyingType = underlyingType; - } - - public string? Namespace { get; } - - public string Name { get; } - - public string FullName { get; } - - public string UnderlyingType { get; } - - public string FormatterName => (this.Namespace == null ? this.Name : this.Namespace + "." + this.Name) + "Formatter"; - } - - public class GenericSerializationInfo : IResolverRegisterInfo, IEquatable - { - public string FullName { get; } - - public string FormatterName { get; } - - public bool IsOpenGenericType { get; } - - public bool Equals(GenericSerializationInfo? other) - { - return this.FullName.Equals(other?.FullName); - } - - public override int GetHashCode() - { - return this.FullName.GetHashCode(); - } - - public GenericSerializationInfo(string fullName, string formatterName, bool isOpenGenericType) - { - FullName = fullName; - FormatterName = formatterName; - IsOpenGenericType = isOpenGenericType; - } - } - - public class UnionSerializationInfo : IResolverRegisterInfo, INamespaceInfo - { - public string? Namespace { get; } - - public string Name { get; } - - public string FullName { get; } - - public string FormatterName => (this.Namespace == null ? this.Name : this.Namespace + "." + this.Name) + "Formatter"; - - public UnionSubTypeInfo[] SubTypes { get; } - - public UnionSerializationInfo(string? @namespace, string name, string fullName, UnionSubTypeInfo[] subTypes) - { - Namespace = @namespace; - Name = name; - FullName = fullName; - SubTypes = subTypes; - } - } - - public class UnionSubTypeInfo - { - public UnionSubTypeInfo(int key, string type) - { - Key = key; - Type = type; - } - - public int Key { get; } - - public string Type { get; } - } -} diff --git a/src/MessagePack.GeneratorCore/CodeAnalysis/TypeCollector.cs b/src/MessagePack.GeneratorCore/CodeAnalysis/TypeCollector.cs deleted file mode 100644 index 5f991d514..000000000 --- a/src/MessagePack.GeneratorCore/CodeAnalysis/TypeCollector.cs +++ /dev/null @@ -1,1071 +0,0 @@ -// Copyright (c) All contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -#pragma warning disable SA1402 // File may only contain a single type -#pragma warning disable SA1649 // File name should match first type name - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using Microsoft.CodeAnalysis; - -namespace MessagePackCompiler.CodeAnalysis -{ - public class MessagePackGeneratorResolveFailedException : Exception - { - public MessagePackGeneratorResolveFailedException(string message) - : base(message) - { - } - } - - internal class ReferenceSymbols - { -#pragma warning disable SA1401 // Fields should be private - internal readonly INamedTypeSymbol? Task; - internal readonly INamedTypeSymbol? TaskOfT; - internal readonly INamedTypeSymbol MessagePackObjectAttribute; - internal readonly INamedTypeSymbol UnionAttribute; - internal readonly INamedTypeSymbol SerializationConstructorAttribute; - internal readonly INamedTypeSymbol KeyAttribute; - internal readonly INamedTypeSymbol IgnoreAttribute; - internal readonly INamedTypeSymbol? IgnoreDataMemberAttribute; - internal readonly INamedTypeSymbol IMessagePackSerializationCallbackReceiver; - internal readonly INamedTypeSymbol MessagePackFormatterAttribute; -#pragma warning restore SA1401 // Fields should be private - - public ReferenceSymbols(Compilation compilation, Action logger) - { - TaskOfT = compilation.GetTypeByMetadataName("System.Threading.Tasks.Task`1"); - if (TaskOfT == null) - { - logger("failed to get metadata of System.Threading.Tasks.Task`1"); - } - - Task = compilation.GetTypeByMetadataName("System.Threading.Tasks.Task"); - if (Task == null) - { - logger("failed to get metadata of System.Threading.Tasks.Task"); - } - - MessagePackObjectAttribute = compilation.GetTypeByMetadataName("MessagePack.MessagePackObjectAttribute") - ?? throw new InvalidOperationException("failed to get metadata of MessagePack.MessagePackObjectAttribute"); - - UnionAttribute = compilation.GetTypeByMetadataName("MessagePack.UnionAttribute") - ?? throw new InvalidOperationException("failed to get metadata of MessagePack.UnionAttribute"); - - SerializationConstructorAttribute = compilation.GetTypeByMetadataName("MessagePack.SerializationConstructorAttribute") - ?? throw new InvalidOperationException("failed to get metadata of MessagePack.SerializationConstructorAttribute"); - - KeyAttribute = compilation.GetTypeByMetadataName("MessagePack.KeyAttribute") - ?? throw new InvalidOperationException("failed to get metadata of MessagePack.KeyAttribute"); - - IgnoreAttribute = compilation.GetTypeByMetadataName("MessagePack.IgnoreMemberAttribute") - ?? throw new InvalidOperationException("failed to get metadata of MessagePack.IgnoreMemberAttribute"); - - IgnoreDataMemberAttribute = compilation.GetTypeByMetadataName("System.Runtime.Serialization.IgnoreDataMemberAttribute"); - if (IgnoreDataMemberAttribute == null) - { - logger("failed to get metadata of System.Runtime.Serialization.IgnoreDataMemberAttribute"); - } - - IMessagePackSerializationCallbackReceiver = compilation.GetTypeByMetadataName("MessagePack.IMessagePackSerializationCallbackReceiver") - ?? throw new InvalidOperationException("failed to get metadata of MessagePack.IMessagePackSerializationCallbackReceiver"); - - MessagePackFormatterAttribute = compilation.GetTypeByMetadataName("MessagePack.MessagePackFormatterAttribute") - ?? throw new InvalidOperationException("failed to get metadata of MessagePack.MessagePackFormatterAttribute"); - } - } - - public class TypeCollector - { - private static readonly SymbolDisplayFormat BinaryWriteFormat = new SymbolDisplayFormat( - genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters, - miscellaneousOptions: SymbolDisplayMiscellaneousOptions.ExpandNullable, - typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameOnly); - - private static readonly SymbolDisplayFormat ShortTypeNameFormat = new SymbolDisplayFormat( - typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypes); - - private readonly bool isForceUseMap; - private readonly ReferenceSymbols typeReferences; - private readonly INamedTypeSymbol[] targetTypes; - private readonly HashSet embeddedTypes = new(new[] - { - "short", - "int", - "long", - "ushort", - "uint", - "ulong", - "float", - "double", - "bool", - "byte", - "sbyte", - "decimal", - "char", - "string", - "object", - "System.Guid", - "System.TimeSpan", - "System.DateTime", - "System.DateTimeOffset", - - "MessagePack.Nil", - - // and arrays - "short[]", - "int[]", - "long[]", - "ushort[]", - "uint[]", - "ulong[]", - "float[]", - "double[]", - "bool[]", - "byte[]", - "sbyte[]", - "decimal[]", - "char[]", - "string[]", - "System.DateTime[]", - "System.ArraySegment", - "System.ArraySegment?", - - // extensions - "UnityEngine.Vector2", - "UnityEngine.Vector3", - "UnityEngine.Vector4", - "UnityEngine.Quaternion", - "UnityEngine.Color", - "UnityEngine.Bounds", - "UnityEngine.Rect", - "UnityEngine.AnimationCurve", - "UnityEngine.RectOffset", - "UnityEngine.Gradient", - "UnityEngine.WrapMode", - "UnityEngine.GradientMode", - "UnityEngine.Keyframe", - "UnityEngine.Matrix4x4", - "UnityEngine.GradientColorKey", - "UnityEngine.GradientAlphaKey", - "UnityEngine.Color32", - "UnityEngine.LayerMask", - "UnityEngine.Vector2Int", - "UnityEngine.Vector3Int", - "UnityEngine.RangeInt", - "UnityEngine.RectInt", - "UnityEngine.BoundsInt", - - "System.Reactive.Unit", - }); - - private readonly Dictionary knownGenericTypes = new() - { -#pragma warning disable SA1509 // Opening braces should not be preceded by blank line - { "System.Collections.Generic.List<>", "global::MessagePack.Formatters.ListFormatter" }, - { "System.Collections.Generic.LinkedList<>", "global::MessagePack.Formatters.LinkedListFormatter" }, - { "System.Collections.Generic.Queue<>", "global::MessagePack.Formatters.QueueFormatter" }, - { "System.Collections.Generic.Stack<>", "global::MessagePack.Formatters.StackFormatter" }, - { "System.Collections.Generic.HashSet<>", "global::MessagePack.Formatters.HashSetFormatter" }, - { "System.Collections.ObjectModel.ReadOnlyCollection<>", "global::MessagePack.Formatters.ReadOnlyCollectionFormatter" }, - { "System.Collections.Generic.IList<>", "global::MessagePack.Formatters.InterfaceListFormatter2" }, - { "System.Collections.Generic.ICollection<>", "global::MessagePack.Formatters.InterfaceCollectionFormatter2" }, - { "System.Collections.Generic.IEnumerable<>", "global::MessagePack.Formatters.InterfaceEnumerableFormatter" }, - { "System.Collections.Generic.Dictionary<,>", "global::MessagePack.Formatters.DictionaryFormatter" }, - { "System.Collections.Generic.IDictionary<,>", "global::MessagePack.Formatters.InterfaceDictionaryFormatter" }, - { "System.Collections.Generic.SortedDictionary<,>", "global::MessagePack.Formatters.SortedDictionaryFormatter" }, - { "System.Collections.Generic.SortedList<,>", "global::MessagePack.Formatters.SortedListFormatter" }, - { "System.Linq.ILookup<,>", "global::MessagePack.Formatters.InterfaceLookupFormatter" }, - { "System.Linq.IGrouping<,>", "global::MessagePack.Formatters.InterfaceGroupingFormatter" }, - { "System.Collections.ObjectModel.ObservableCollection<>", "global::MessagePack.Formatters.ObservableCollectionFormatter" }, - { "System.Collections.ObjectModel.ReadOnlyObservableCollection<>", "global::MessagePack.Formatters.ReadOnlyObservableCollectionFormatter" }, - { "System.Collections.Generic.IReadOnlyList<>", "global::MessagePack.Formatters.InterfaceReadOnlyListFormatter" }, - { "System.Collections.Generic.IReadOnlyCollection<>", "global::MessagePack.Formatters.InterfaceReadOnlyCollectionFormatter" }, - { "System.Collections.Generic.ISet<>", "global::MessagePack.Formatters.InterfaceSetFormatter" }, - { "System.Collections.Concurrent.ConcurrentBag<>", "global::MessagePack.Formatters.ConcurrentBagFormatter" }, - { "System.Collections.Concurrent.ConcurrentQueue<>", "global::MessagePack.Formatters.ConcurrentQueueFormatter" }, - { "System.Collections.Concurrent.ConcurrentStack<>", "global::MessagePack.Formatters.ConcurrentStackFormatter" }, - { "System.Collections.ObjectModel.ReadOnlyDictionary<,>", "global::MessagePack.Formatters.ReadOnlyDictionaryFormatter" }, - { "System.Collections.Generic.IReadOnlyDictionary<,>", "global::MessagePack.Formatters.InterfaceReadOnlyDictionaryFormatter" }, - { "System.Collections.Concurrent.ConcurrentDictionary<,>", "global::MessagePack.Formatters.ConcurrentDictionaryFormatter" }, - { "System.Lazy<>", "global::MessagePack.Formatters.LazyFormatter" }, - { "System.Threading.Tasks<>", "global::MessagePack.Formatters.TaskValueFormatter" }, - - { "System.Tuple<>", "global::MessagePack.Formatters.TupleFormatter" }, - { "System.Tuple<,>", "global::MessagePack.Formatters.TupleFormatter" }, - { "System.Tuple<,,>", "global::MessagePack.Formatters.TupleFormatter" }, - { "System.Tuple<,,,>", "global::MessagePack.Formatters.TupleFormatter" }, - { "System.Tuple<,,,,>", "global::MessagePack.Formatters.TupleFormatter" }, - { "System.Tuple<,,,,,>", "global::MessagePack.Formatters.TupleFormatter" }, - { "System.Tuple<,,,,,,>", "global::MessagePack.Formatters.TupleFormatter" }, - { "System.Tuple<,,,,,,,>", "global::MessagePack.Formatters.TupleFormatter" }, - - { "System.ValueTuple<>", "global::MessagePack.Formatters.ValueTupleFormatter" }, - { "System.ValueTuple<,>", "global::MessagePack.Formatters.ValueTupleFormatter" }, - { "System.ValueTuple<,,>", "global::MessagePack.Formatters.ValueTupleFormatter" }, - { "System.ValueTuple<,,,>", "global::MessagePack.Formatters.ValueTupleFormatter" }, - { "System.ValueTuple<,,,,>", "global::MessagePack.Formatters.ValueTupleFormatter" }, - { "System.ValueTuple<,,,,,>", "global::MessagePack.Formatters.ValueTupleFormatter" }, - { "System.ValueTuple<,,,,,,>", "global::MessagePack.Formatters.ValueTupleFormatter" }, - { "System.ValueTuple<,,,,,,,>", "global::MessagePack.Formatters.ValueTupleFormatter" }, - - { "System.Collections.Generic.KeyValuePair<,>", "global::MessagePack.Formatters.KeyValuePairFormatter" }, - { "System.Threading.Tasks.ValueTask<>", "global::MessagePack.Formatters.KeyValuePairFormatter" }, - { "System.ArraySegment<>", "global::MessagePack.Formatters.ArraySegmentFormatter" }, - - // extensions - { "System.Collections.Immutable.ImmutableArray<>", "global::MessagePack.ImmutableCollection.ImmutableArrayFormatter" }, - { "System.Collections.Immutable.ImmutableList<>", "global::MessagePack.ImmutableCollection.ImmutableListFormatter" }, - { "System.Collections.Immutable.ImmutableDictionary<,>", "global::MessagePack.ImmutableCollection.ImmutableDictionaryFormatter" }, - { "System.Collections.Immutable.ImmutableHashSet<>", "global::MessagePack.ImmutableCollection.ImmutableHashSetFormatter" }, - { "System.Collections.Immutable.ImmutableSortedDictionary<,>", "global::MessagePack.ImmutableCollection.ImmutableSortedDictionaryFormatter" }, - { "System.Collections.Immutable.ImmutableSortedSet<>", "global::MessagePack.ImmutableCollection.ImmutableSortedSetFormatter" }, - { "System.Collections.Immutable.ImmutableQueue<>", "global::MessagePack.ImmutableCollection.ImmutableQueueFormatter" }, - { "System.Collections.Immutable.ImmutableStack<>", "global::MessagePack.ImmutableCollection.ImmutableStackFormatter" }, - { "System.Collections.Immutable.IImmutableList<>", "global::MessagePack.ImmutableCollection.InterfaceImmutableListFormatter" }, - { "System.Collections.Immutable.IImmutableDictionary<,>", "global::MessagePack.ImmutableCollection.InterfaceImmutableDictionaryFormatter" }, - { "System.Collections.Immutable.IImmutableQueue<>", "global::MessagePack.ImmutableCollection.InterfaceImmutableQueueFormatter" }, - { "System.Collections.Immutable.IImmutableSet<>", "global::MessagePack.ImmutableCollection.InterfaceImmutableSetFormatter" }, - { "System.Collections.Immutable.IImmutableStack<>", "global::MessagePack.ImmutableCollection.InterfaceImmutableStackFormatter" }, - - { "Reactive.Bindings.ReactiveProperty<>", "global::MessagePack.ReactivePropertyExtension.ReactivePropertyFormatter" }, - { "Reactive.Bindings.IReactiveProperty<>", "global::MessagePack.ReactivePropertyExtension.InterfaceReactivePropertyFormatter" }, - { "Reactive.Bindings.IReadOnlyReactiveProperty<>", "global::MessagePack.ReactivePropertyExtension.InterfaceReadOnlyReactivePropertyFormatter" }, - { "Reactive.Bindings.ReactiveCollection<>", "global::MessagePack.ReactivePropertyExtension.ReactiveCollectionFormatter" }, -#pragma warning restore SA1509 // Opening braces should not be preceded by blank line - }; - - private readonly bool disallowInternal; - - private readonly HashSet externalIgnoreTypeNames; - - // visitor workspace: -#pragma warning disable RS1024 // Compare symbols correctly (https://github.com/dotnet/roslyn-analyzers/issues/5246) - private readonly HashSet alreadyCollected = new(SymbolEqualityComparer.Default); -#pragma warning restore RS1024 // Compare symbols correctly - private readonly List collectedObjectInfo = new(); - private readonly List collectedEnumInfo = new(); - private readonly List collectedGenericInfo = new(); - private readonly List collectedUnionInfo = new(); - - private readonly Compilation compilation; - - public TypeCollector(Compilation compilation, bool disallowInternal, bool isForceUseMap, string[]? ignoreTypeNames, Action logger) - { - this.typeReferences = new ReferenceSymbols(compilation, logger); - this.disallowInternal = disallowInternal; - this.isForceUseMap = isForceUseMap; - this.externalIgnoreTypeNames = new HashSet(ignoreTypeNames ?? Array.Empty()); - this.compilation = compilation; - - targetTypes = compilation.GetNamedTypeSymbols() - .Where(x => - { - if (x.DeclaredAccessibility == Accessibility.Public) - { - return true; - } - - if (!disallowInternal) - { - return x.DeclaredAccessibility == Accessibility.Friend; - } - - return false; - }) - .Where(x => - ((x.TypeKind == TypeKind.Interface) && x.GetAttributes().Any(x2 => x2.AttributeClass.ApproximatelyEqual(typeReferences.UnionAttribute))) - || ((x.TypeKind == TypeKind.Class && x.IsAbstract) && x.GetAttributes().Any(x2 => x2.AttributeClass.ApproximatelyEqual(typeReferences.UnionAttribute))) - || ((x.TypeKind == TypeKind.Class) && x.GetAttributes().Any(x2 => x2.AttributeClass.ApproximatelyEqual(typeReferences.MessagePackObjectAttribute))) - || ((x.TypeKind == TypeKind.Struct) && x.GetAttributes().Any(x2 => x2.AttributeClass.ApproximatelyEqual(typeReferences.MessagePackObjectAttribute)))) - .ToArray(); - } - - private void ResetWorkspace() - { - this.alreadyCollected.Clear(); - this.collectedObjectInfo.Clear(); - this.collectedEnumInfo.Clear(); - this.collectedGenericInfo.Clear(); - this.collectedUnionInfo.Clear(); - } - - // EntryPoint - public (ObjectSerializationInfo[] ObjectInfo, EnumSerializationInfo[] EnumInfo, GenericSerializationInfo[] GenericInfo, UnionSerializationInfo[] UnionInfo) Collect() - { - this.ResetWorkspace(); - - foreach (INamedTypeSymbol item in this.targetTypes) - { - this.CollectCore(item); - } - - return ( - this.collectedObjectInfo.OrderBy(x => x.FullName).ToArray(), - this.collectedEnumInfo.OrderBy(x => x.FullName).ToArray(), - this.collectedGenericInfo.Distinct().OrderBy(x => x.FullName).ToArray(), - this.collectedUnionInfo.OrderBy(x => x.FullName).ToArray()); - } - - // Gate of recursive collect - private void CollectCore(ITypeSymbol typeSymbol) - { - if (!this.alreadyCollected.Add(typeSymbol)) - { - return; - } - - var typeSymbolString = typeSymbol.WithNullableAnnotation(NullableAnnotation.NotAnnotated).ToString() ?? throw new InvalidOperationException(); - if (this.embeddedTypes.Contains(typeSymbolString)) - { - return; - } - - if (this.externalIgnoreTypeNames.Contains(typeSymbolString)) - { - return; - } - - if (typeSymbol is IArrayTypeSymbol arrayTypeSymbol) - { - this.CollectArray((IArrayTypeSymbol)ToTupleUnderlyingType(arrayTypeSymbol)); - return; - } - - if (!this.IsAllowAccessibility(typeSymbol)) - { - return; - } - - if (!(typeSymbol is INamedTypeSymbol type)) - { - return; - } - - var customFormatterAttr = typeSymbol.GetAttributes().FirstOrDefault(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.MessagePackFormatterAttribute)); - if (customFormatterAttr != null) - { - return; - } - - if (type.EnumUnderlyingType != null) - { - this.CollectEnum(type, type.EnumUnderlyingType); - return; - } - - if (type.IsGenericType) - { - this.CollectGeneric((INamedTypeSymbol)ToTupleUnderlyingType(type)); - return; - } - - if (type.Locations[0].IsInMetadata) - { - return; - } - - if (type.TypeKind == TypeKind.Interface || (type.TypeKind == TypeKind.Class && type.IsAbstract)) - { - this.CollectUnion(type); - return; - } - - this.CollectObject(type); - } - - private void CollectEnum(INamedTypeSymbol type, ISymbol enumUnderlyingType) - { - var info = new EnumSerializationInfo(type.ContainingNamespace.IsGlobalNamespace ? null : type.ContainingNamespace.ToDisplayString(), type.ToDisplayString(ShortTypeNameFormat).Replace(".", "_"), type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), enumUnderlyingType.ToDisplayString(BinaryWriteFormat)); - this.collectedEnumInfo.Add(info); - } - - private void CollectUnion(INamedTypeSymbol type) - { - ImmutableArray[] unionAttrs = type.GetAttributes().Where(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.UnionAttribute)).Select(x => x.ConstructorArguments).ToArray(); - if (unionAttrs.Length == 0) - { - throw new MessagePackGeneratorResolveFailedException("Serialization Type must mark UnionAttribute." + " type: " + type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); - } - - // 0, Int 1, SubType - UnionSubTypeInfo UnionSubTypeInfoSelector(ImmutableArray x) - { - if (!(x[0] is { Value: int key }) || !(x[1] is { Value: ITypeSymbol typeSymbol })) - { - throw new NotSupportedException("AOT code generation only supports UnionAttribute that uses a Type parameter, but the " + type.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat) + " type uses an unsupported parameter."); - } - - var typeName = typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); - return new UnionSubTypeInfo(key, typeName); - } - - var info = new UnionSerializationInfo(type.ContainingNamespace.IsGlobalNamespace ? null : type.ContainingNamespace.ToDisplayString(), type.Name, type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), unionAttrs.Select(UnionSubTypeInfoSelector).OrderBy(x => x.Key).ToArray()); - - this.collectedUnionInfo.Add(info); - } - - private void CollectGenericUnion(INamedTypeSymbol type) - { - var unionAttrs = type.GetAttributes().Where(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.UnionAttribute)).Select(x => x.ConstructorArguments); - using var enumerator = unionAttrs.GetEnumerator(); - if (!enumerator.MoveNext()) - { - return; - } - - do - { - var x = enumerator.Current; - if (x[1] is { Value: INamedTypeSymbol unionType } && alreadyCollected.Contains(unionType) == false) - { - CollectCore(unionType); - } - } - while (enumerator.MoveNext()); - } - - private void CollectArray(IArrayTypeSymbol array) - { - ITypeSymbol elemType = array.ElementType; - this.CollectCore(elemType); - - var fullName = array.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); - var elementTypeDisplayName = elemType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); - string formatterName; - if (array.IsSZArray) - { - formatterName = "global::MessagePack.Formatters.ArrayFormatter<" + elementTypeDisplayName + ">"; - } - else - { - formatterName = array.Rank switch - { - 2 => "global::MessagePack.Formatters.TwoDimensionalArrayFormatter<" + elementTypeDisplayName + ">", - 3 => "global::MessagePack.Formatters.ThreeDimensionalArrayFormatter<" + elementTypeDisplayName + ">", - 4 => "global::MessagePack.Formatters.FourDimensionalArrayFormatter<" + elementTypeDisplayName + ">", - _ => throw new InvalidOperationException("does not supports array dimension, " + fullName), - }; - } - - var info = new GenericSerializationInfo(fullName, formatterName, elemType is ITypeParameterSymbol); - this.collectedGenericInfo.Add(info); - } - - private ITypeSymbol ToTupleUnderlyingType(ITypeSymbol typeSymbol) - { - if (typeSymbol is IArrayTypeSymbol array) - { - return compilation.CreateArrayTypeSymbol(ToTupleUnderlyingType(array.ElementType), array.Rank); - } - - if (typeSymbol is not INamedTypeSymbol namedType || !namedType.IsGenericType) - { - return typeSymbol; - } - - namedType = namedType.TupleUnderlyingType ?? namedType; - var newTypeArguments = namedType.TypeArguments.Select(ToTupleUnderlyingType).ToArray(); - if (!namedType.TypeArguments.SequenceEqual(newTypeArguments)) - { - return namedType.ConstructedFrom.Construct(newTypeArguments); - } - - return namedType; - } - - private void CollectGeneric(INamedTypeSymbol type) - { - INamedTypeSymbol genericType = type.ConstructUnboundGenericType(); - var genericTypeString = genericType.ToDisplayString(); - var fullName = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); - var isOpenGenericType = IsOpenGenericTypeRecursively(type); - - // special case - if (fullName == "global::System.ArraySegment" || fullName == "global::System.ArraySegment?") - { - return; - } - - // nullable - if (genericTypeString == "T?") - { - var firstTypeArgument = type.TypeArguments[0]; - this.CollectCore(firstTypeArgument); - - if (this.embeddedTypes.Contains(firstTypeArgument.ToString()!)) - { - return; - } - - var info = new GenericSerializationInfo(type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), "global::MessagePack.Formatters.NullableFormatter<" + firstTypeArgument.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) + ">", isOpenGenericType); - this.collectedGenericInfo.Add(info); - return; - } - - // collection - if (this.knownGenericTypes.TryGetValue(genericTypeString, out var formatter)) - { - foreach (ITypeSymbol item in type.TypeArguments) - { - this.CollectCore(item); - } - - var typeArgs = string.Join(", ", type.TypeArguments.Select(x => x.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))); - var f = formatter.Replace("TREPLACE", typeArgs); - - var info = new GenericSerializationInfo(type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), f, isOpenGenericType); - - this.collectedGenericInfo.Add(info); - - if (genericTypeString != "System.Linq.ILookup<,>") - { - return; - } - - formatter = this.knownGenericTypes["System.Linq.IGrouping<,>"]; - f = formatter.Replace("TREPLACE", typeArgs); - - var groupingInfo = new GenericSerializationInfo("global::System.Linq.IGrouping<" + typeArgs + ">", f, isOpenGenericType); - this.collectedGenericInfo.Add(groupingInfo); - - formatter = this.knownGenericTypes["System.Collections.Generic.IEnumerable<>"]; - typeArgs = type.TypeArguments[1].ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); - f = formatter.Replace("TREPLACE", typeArgs); - - var enumerableInfo = new GenericSerializationInfo("global::System.Collections.Generic.IEnumerable<" + typeArgs + ">", f, isOpenGenericType); - this.collectedGenericInfo.Add(enumerableInfo); - return; - } - - // Generic types - if (type.IsDefinition) - { - this.CollectGenericUnion(type); - this.CollectObject(type); - return; - } - else - { - // Collect substituted types for the properties and fields. - // NOTE: It is used to register formatters from nested generic type. - // However, closed generic types such as `Foo` are not registered as a formatter. - GetObjectInfo(type); - - // Collect generic type definition, that is not collected when it is defined outside target project. - CollectCore(type.OriginalDefinition); - } - - // Collect substituted types for the type parameters (e.g. Bar in Foo) - foreach (var item in type.TypeArguments) - { - this.CollectCore(item); - } - - var formatterBuilder = new StringBuilder(); - if (!type.ContainingNamespace.IsGlobalNamespace) - { - formatterBuilder.Append(type.ContainingNamespace.ToDisplayString() + "."); - } - - formatterBuilder.Append(type.Name); - formatterBuilder.Append("Formatter<"); - var typeArgumentIterator = type.TypeArguments.GetEnumerator(); - { - if (typeArgumentIterator.MoveNext()) - { - formatterBuilder.Append(typeArgumentIterator.Current.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); - } - - while (typeArgumentIterator.MoveNext()) - { - formatterBuilder.Append(", "); - formatterBuilder.Append(typeArgumentIterator.Current.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); - } - } - - formatterBuilder.Append('>'); - - var genericSerializationInfo = new GenericSerializationInfo(type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), formatterBuilder.ToString(), isOpenGenericType); - this.collectedGenericInfo.Add(genericSerializationInfo); - } - - private void CollectObject(INamedTypeSymbol type) - { - ObjectSerializationInfo info = GetObjectInfo(type); - collectedObjectInfo.Add(info); - } - - private ObjectSerializationInfo GetObjectInfo(INamedTypeSymbol type) - { - var isClass = !type.IsValueType; - var isOpenGenericType = type.IsGenericType; - - AttributeData contractAttr = type.GetAttributes().FirstOrDefault(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.MessagePackObjectAttribute)) - ?? throw new MessagePackGeneratorResolveFailedException("Serialization Object must mark MessagePackObjectAttribute." + " type: " + type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); - - var isIntKey = true; - var intMembers = new Dictionary(); - var stringMembers = new Dictionary(); - - if (this.isForceUseMap || (contractAttr.ConstructorArguments[0] is { Value: bool firstConstructorArgument } && firstConstructorArgument)) - { - // All public members are serialize target except [Ignore] member. - isIntKey = false; - - var hiddenIntKey = 0; - - foreach (IPropertySymbol item in type.GetAllMembers().OfType().Where(x => !x.IsOverride)) - { - if (item.GetAttributes().Any(x => (x.AttributeClass.ApproximatelyEqual(this.typeReferences.IgnoreAttribute) || x.AttributeClass?.Name == this.typeReferences.IgnoreDataMemberAttribute?.Name))) - { - continue; - } - - var isReadable = item.GetMethod != null && item.GetMethod.DeclaredAccessibility == Accessibility.Public && !item.IsStatic; - var isWritable = item.SetMethod != null && item.SetMethod.DeclaredAccessibility == Accessibility.Public && !item.IsStatic; - if (!isReadable && !isWritable) - { - continue; - } - - var customFormatterAttr = item.GetAttributes().FirstOrDefault(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.MessagePackFormatterAttribute))?.ConstructorArguments[0].Value as INamedTypeSymbol; - var keyAttribute = item.GetAttributes() - .FirstOrDefault(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.KeyAttribute)); - - var stringKey = keyAttribute?.ConstructorArguments.Length > 0 - ? keyAttribute.ConstructorArguments[0].Value as string ?? item.Name - : item.Name; - - var member = new MemberSerializationInfo(true, isWritable, isReadable, hiddenIntKey++, stringKey, item.Name, item.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), item.Type.ToDisplayString(BinaryWriteFormat), customFormatterAttr?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); - stringMembers.Add(member.StringKey, member); - - this.CollectCore(item.Type); // recursive collect - } - - foreach (IFieldSymbol item in type.GetAllMembers().OfType()) - { - if (item.GetAttributes().Any(x => (x.AttributeClass.ApproximatelyEqual(this.typeReferences.IgnoreAttribute) || x.AttributeClass?.Name == this.typeReferences.IgnoreDataMemberAttribute?.Name))) - { - continue; - } - - if (item.IsImplicitlyDeclared) - { - continue; - } - - var isReadable = item.DeclaredAccessibility == Accessibility.Public && !item.IsStatic; - var isWritable = item.DeclaredAccessibility == Accessibility.Public && !item.IsReadOnly && !item.IsStatic; - if (!isReadable && !isWritable) - { - continue; - } - - var customFormatterAttr = item.GetAttributes().FirstOrDefault(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.MessagePackFormatterAttribute))?.ConstructorArguments[0].Value as INamedTypeSymbol; - var keyAttribute = item.GetAttributes() - .FirstOrDefault(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.KeyAttribute)); - - var stringKey = keyAttribute?.ConstructorArguments.Length > 0 - ? keyAttribute.ConstructorArguments[0].Value as string ?? item.Name - : item.Name; - - var member = new MemberSerializationInfo(false, isWritable, isReadable, hiddenIntKey++, stringKey, item.Name, item.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), item.Type.ToDisplayString(BinaryWriteFormat), customFormatterAttr?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); - stringMembers.Add(member.StringKey, member); - this.CollectCore(item.Type); // recursive collect - } - } - else - { - // Only KeyAttribute members - var searchFirst = true; - var hiddenIntKey = 0; - - foreach (IPropertySymbol item in type.GetAllMembers().OfType()) - { - if (item.IsIndexer) - { - continue; // .tt files don't generate good code for this yet: https://github.com/neuecc/MessagePack-CSharp/issues/390 - } - - if (item.GetAttributes().Any(x => - { - var typeReferencesIgnoreDataMemberAttribute = this.typeReferences.IgnoreDataMemberAttribute; - return typeReferencesIgnoreDataMemberAttribute != null && (x.AttributeClass.ApproximatelyEqual(this.typeReferences.IgnoreAttribute) || x.AttributeClass.ApproximatelyEqual(typeReferencesIgnoreDataMemberAttribute)); - })) - { - continue; - } - - var isReadable = item.GetMethod != null && item.GetMethod.DeclaredAccessibility == Accessibility.Public && !item.IsStatic; - var isWritable = item.SetMethod != null && item.SetMethod.DeclaredAccessibility == Accessibility.Public && !item.IsStatic; - if (!isReadable && !isWritable) - { - continue; - } - - var customFormatterAttr = item.GetAttributes().FirstOrDefault(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.MessagePackFormatterAttribute))?.ConstructorArguments[0].Value as INamedTypeSymbol; - var key = item.GetAttributes().FirstOrDefault(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.KeyAttribute))?.ConstructorArguments[0] - ?? throw new MessagePackGeneratorResolveFailedException("all public members must mark KeyAttribute or IgnoreMemberAttribute." + " type: " + type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) + " member:" + item.Name); - - var intKey = key is { Value: int intKeyValue } ? intKeyValue : default(int?); - var stringKey = key is { Value: string stringKeyValue } ? stringKeyValue : default; - if (intKey == null && stringKey == null) - { - throw new MessagePackGeneratorResolveFailedException("both IntKey and StringKey are null." + " type: " + type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) + " member:" + item.Name); - } - - if (searchFirst) - { - searchFirst = false; - isIntKey = intKey != null; - } - else - { - if ((isIntKey && intKey == null) || (!isIntKey && stringKey == null)) - { - throw new MessagePackGeneratorResolveFailedException("all members key type must be same." + " type: " + type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) + " member:" + item.Name); - } - } - - if (isIntKey) - { - if (intMembers.ContainsKey(intKey!.Value)) - { - throw new MessagePackGeneratorResolveFailedException("key is duplicated, all members key must be unique." + " type: " + type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) + " member:" + item.Name); - } - - var member = new MemberSerializationInfo(true, isWritable, isReadable, intKey!.Value, item.Name, item.Name, item.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), item.Type.ToDisplayString(BinaryWriteFormat), customFormatterAttr?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); - intMembers.Add(member.IntKey, member); - } - else - { - if (stringMembers.ContainsKey(stringKey!)) - { - throw new MessagePackGeneratorResolveFailedException("key is duplicated, all members key must be unique." + " type: " + type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) + " member:" + item.Name); - } - - var member = new MemberSerializationInfo(true, isWritable, isReadable, hiddenIntKey++, stringKey!, item.Name, item.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), item.Type.ToDisplayString(BinaryWriteFormat), customFormatterAttr?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); - stringMembers.Add(member.StringKey, member); - } - - var messagePackFormatter = item.GetAttributes().FirstOrDefault(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.MessagePackFormatterAttribute))?.ConstructorArguments[0]; - - if (messagePackFormatter == null) - { - this.CollectCore(item.Type); // recursive collect - } - } - - foreach (IFieldSymbol item in type.GetAllMembers().OfType()) - { - if (item.IsImplicitlyDeclared) - { - continue; - } - - if (item.GetAttributes().Any(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.IgnoreAttribute))) - { - continue; - } - - var isReadable = item.DeclaredAccessibility == Accessibility.Public && !item.IsStatic; - var isWritable = item.DeclaredAccessibility == Accessibility.Public && !item.IsReadOnly && !item.IsStatic; - if (!isReadable && !isWritable) - { - continue; - } - - var customFormatterAttr = item.GetAttributes().FirstOrDefault(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.MessagePackFormatterAttribute))?.ConstructorArguments[0].Value as INamedTypeSymbol; - var key = item.GetAttributes().FirstOrDefault(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.KeyAttribute))?.ConstructorArguments[0] - ?? throw new MessagePackGeneratorResolveFailedException("all public members must mark KeyAttribute or IgnoreMemberAttribute." + " type: " + type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) + " member:" + item.Name); - - var intKey = key is { Value: int intKeyValue } ? intKeyValue : default(int?); - var stringKey = key is { Value: string stringKeyValue } ? stringKeyValue : default; - if (intKey == null && stringKey == null) - { - throw new MessagePackGeneratorResolveFailedException("both IntKey and StringKey are null." + " type: " + type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) + " member:" + item.Name); - } - - if (searchFirst) - { - searchFirst = false; - isIntKey = intKey != null; - } - else - { - if ((isIntKey && intKey == null) || (!isIntKey && stringKey == null)) - { - throw new MessagePackGeneratorResolveFailedException("all members key type must be same." + " type: " + type.Name + " member:" + item.Name); - } - } - - if (isIntKey) - { - if (intMembers.ContainsKey(intKey!.Value)) - { - throw new MessagePackGeneratorResolveFailedException("key is duplicated, all members key must be unique." + " type: " + type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) + " member:" + item.Name); - } - - var member = new MemberSerializationInfo(true, isWritable, isReadable, intKey!.Value, item.Name, item.Name, item.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), item.Type.ToDisplayString(BinaryWriteFormat), customFormatterAttr?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); - intMembers.Add(member.IntKey, member); - } - else - { - if (stringMembers.ContainsKey(stringKey!)) - { - throw new MessagePackGeneratorResolveFailedException("key is duplicated, all members key must be unique." + " type: " + type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) + " member:" + item.Name); - } - - var member = new MemberSerializationInfo(true, isWritable, isReadable, hiddenIntKey++, stringKey!, item.Name, item.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), item.Type.ToDisplayString(BinaryWriteFormat), customFormatterAttr?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); - stringMembers.Add(member.StringKey, member); - } - - this.CollectCore(item.Type); // recursive collect - } - } - - // GetConstructor - var ctorEnumerator = default(IEnumerator); - var ctor = type.Constructors.Where(x => x.DeclaredAccessibility == Accessibility.Public).SingleOrDefault(x => x.GetAttributes().Any(y => y.AttributeClass != null && y.AttributeClass.ApproximatelyEqual(this.typeReferences.SerializationConstructorAttribute))); - if (ctor == null) - { - ctorEnumerator = type.Constructors.Where(x => x.DeclaredAccessibility == Accessibility.Public).OrderByDescending(x => x.Parameters.Length).GetEnumerator(); - - if (ctorEnumerator.MoveNext()) - { - ctor = ctorEnumerator.Current; - } - } - - // struct allows null ctor - if (ctor == null && isClass) - { - throw new MessagePackGeneratorResolveFailedException("can't find public constructor. type:" + type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); - } - - var constructorParameters = new List(); - if (ctor != null) - { - var constructorLookupDictionary = stringMembers.ToLookup(x => x.Key, x => x, StringComparer.OrdinalIgnoreCase); - do - { - constructorParameters.Clear(); - var ctorParamIndex = 0; - foreach (IParameterSymbol item in ctor!.Parameters) - { - MemberSerializationInfo paramMember; - if (isIntKey) - { - if (intMembers.TryGetValue(ctorParamIndex, out paramMember!)) - { - if (item.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == paramMember.Type && paramMember.IsReadable) - { - constructorParameters.Add(paramMember); - } - else - { - if (ctorEnumerator != null) - { - ctor = null; - continue; - } - else - { - throw new MessagePackGeneratorResolveFailedException("can't find matched constructor parameter, parameterType mismatch. type:" + type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) + " parameterIndex:" + ctorParamIndex + " parameterType:" + item.Type.Name); - } - } - } - else - { - if (ctorEnumerator != null) - { - ctor = null; - continue; - } - else - { - throw new MessagePackGeneratorResolveFailedException("can't find matched constructor parameter, index not found. type:" + type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) + " parameterIndex:" + ctorParamIndex); - } - } - } - else - { - IEnumerable> hasKey = constructorLookupDictionary[item.Name]; - using var enumerator = hasKey.GetEnumerator(); - - // hasKey.Count() == 0 - if (!enumerator.MoveNext()) - { - if (ctorEnumerator == null) - { - throw new MessagePackGeneratorResolveFailedException("can't find matched constructor parameter, index not found. type:" + type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) + " parameterName:" + item.Name); - } - - ctor = null; - continue; - } - - var first = enumerator.Current.Value; - - // hasKey.Count() != 1 - if (enumerator.MoveNext()) - { - if (ctorEnumerator == null) - { - throw new MessagePackGeneratorResolveFailedException("duplicate matched constructor parameter name:" + type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) + " parameterName:" + item.Name + " parameterType:" + item.Type.Name); - } - - ctor = null; - continue; - } - - paramMember = first; - if (item.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == paramMember.Type && paramMember.IsReadable) - { - constructorParameters.Add(paramMember); - } - else - { - if (ctorEnumerator == null) - { - throw new MessagePackGeneratorResolveFailedException("can't find matched constructor parameter, parameterType mismatch. type:" + type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) + " parameterName:" + item.Name + " parameterType:" + item.Type.Name); - } - - ctor = null; - continue; - } - } - - ctorParamIndex++; - } - } - while (TryGetNextConstructor(ctorEnumerator, ref ctor)); - - if (ctor == null) - { - throw new MessagePackGeneratorResolveFailedException("can't find matched constructor. type:" + type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); - } - } - - var hasSerializationConstructor = type.AllInterfaces.Any(x => x.ApproximatelyEqual(this.typeReferences.IMessagePackSerializationCallbackReceiver)); - var needsCastOnBefore = true; - var needsCastOnAfter = true; - if (hasSerializationConstructor) - { - needsCastOnBefore = !type.GetMembers("OnBeforeSerialize").Any(); - needsCastOnAfter = !type.GetMembers("OnAfterDeserialize").Any(); - } - - var info = new ObjectSerializationInfo(isClass, isOpenGenericType, isOpenGenericType ? type.TypeParameters.Select(ToGenericTypeParameterInfo).ToArray() : Array.Empty(), constructorParameters.ToArray(), isIntKey, isIntKey ? intMembers.Values.ToArray() : stringMembers.Values.ToArray(), isOpenGenericType ? GetGenericFormatterClassName(type) : GetMinimallyQualifiedClassName(type), type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), type.ContainingNamespace.IsGlobalNamespace ? null : type.ContainingNamespace.ToDisplayString(), hasSerializationConstructor, needsCastOnAfter, needsCastOnBefore); - - return info; - } - - private static GenericTypeParameterInfo ToGenericTypeParameterInfo(ITypeParameterSymbol typeParameter) - { - var constraints = new List(); - - // `notnull`, `unmanaged`, `class`, `struct` constraint must come before any constraints. - if (typeParameter.HasNotNullConstraint) - { - constraints.Add("notnull"); - } - - if (typeParameter.HasReferenceTypeConstraint) - { - constraints.Add(typeParameter.ReferenceTypeConstraintNullableAnnotation == NullableAnnotation.Annotated ? "class?" : "class"); - } - - if (typeParameter.HasValueTypeConstraint) - { - constraints.Add(typeParameter.HasUnmanagedTypeConstraint ? "unmanaged" : "struct"); - } - - // constraint types (IDisposable, IEnumerable ...) - foreach (var t in typeParameter.ConstraintTypes) - { - var constraintTypeFullName = t.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat.AddMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier)); - constraints.Add(constraintTypeFullName); - } - - // `new()` constraint must be last in constraints. - if (typeParameter.HasConstructorConstraint) - { - constraints.Add("new()"); - } - - return new GenericTypeParameterInfo(typeParameter.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), string.Join(", ", constraints)); - } - - private static string GetGenericFormatterClassName(INamedTypeSymbol type) - { - return type.Name; - } - - private static string GetMinimallyQualifiedClassName(INamedTypeSymbol type) - { - var name = type.ContainingType is object ? GetMinimallyQualifiedClassName(type.ContainingType) + "_" : string.Empty; - name += type.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat); - name = name.Replace('.', '_'); - name = name.Replace('<', '_'); - name = name.Replace('>', '_'); - name = Regex.Replace(name, @"\[([,])*\]", match => $"Array{match.Length - 1}"); - name = name.Replace("?", string.Empty); - return name; - } - - private static bool TryGetNextConstructor(IEnumerator? ctorEnumerator, ref IMethodSymbol? ctor) - { - if (ctorEnumerator == null || ctor != null) - { - return false; - } - - if (ctorEnumerator.MoveNext()) - { - ctor = ctorEnumerator.Current; - return true; - } - else - { - ctor = null; - return false; - } - } - - private bool IsAllowAccessibility(ITypeSymbol symbol) - { - do - { - if (symbol.DeclaredAccessibility != Accessibility.Public) - { - if (this.disallowInternal) - { - return false; - } - - if (symbol.DeclaredAccessibility != Accessibility.Internal) - { - return true; - } - } - - symbol = symbol.ContainingType; - } - while (symbol != null); - - return true; - } - - private bool IsOpenGenericTypeRecursively(INamedTypeSymbol type) - { - return type.IsGenericType && type.TypeArguments.Any(x => x is ITypeParameterSymbol || (x is INamedTypeSymbol symbol && IsOpenGenericTypeRecursively(symbol))); - } - } -} diff --git a/src/MessagePack.GeneratorCore/CodeGenerator.cs b/src/MessagePack.GeneratorCore/CodeGenerator.cs deleted file mode 100644 index 1f48973e3..000000000 --- a/src/MessagePack.GeneratorCore/CodeGenerator.cs +++ /dev/null @@ -1,319 +0,0 @@ -// Copyright (c) All contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MessagePackCompiler.CodeAnalysis; -using MessagePackCompiler.Generator; -using Microsoft.CodeAnalysis; - -namespace MessagePackCompiler -{ - public class CodeGenerator - { - private static readonly HashSet InvalidFileCharSet = new(Path.GetInvalidFileNameChars()); - - private static readonly Encoding NoBomUtf8 = new UTF8Encoding(false); - - private readonly Action logger; - - public CodeGenerator(Action logger, CancellationToken cancellationToken) - { - this.logger = logger; - } - - /// - /// Generates the specialized resolver and formatters for the types that require serialization in a given compilation. - /// - /// The compilation to read types from as an input to code generation. - /// The name of the generated source file. - /// The resolver name. - /// The namespace for the generated type to be created in. May be null. - /// A boolean value that indicates whether all formatters should use property maps instead of more compact arrays. - /// A comma-delimited list of symbols that should surround redundant generated files. May be null. - /// May be null. - /// A task that indicates when generation has completed. - public async Task GenerateFileAsync( - Compilation compilation, - string output, - string resolverName, - string? @namespace, - bool useMapMode, - string? multipleIfDirectiveOutputSymbols, - string[]? externalIgnoreTypeNames) - { - var namespaceDot = string.IsNullOrWhiteSpace(@namespace) ? string.Empty : @namespace + "."; - var multipleOutputSymbols = multipleIfDirectiveOutputSymbols?.Split(',') ?? Array.Empty(); - - var sw = Stopwatch.StartNew(); - - foreach (var multiOutputSymbol in multipleOutputSymbols.Length == 0 ? new[] { string.Empty } : multipleOutputSymbols) - { - logger("Project Compilation Start:" + compilation.AssemblyName); - - var collector = new TypeCollector(compilation, true, useMapMode, externalIgnoreTypeNames, Console.WriteLine); - - logger("Project Compilation Complete:" + sw.Elapsed.ToString()); - - sw.Restart(); - logger("Method Collect Start"); - - var (objectInfo, enumInfo, genericInfo, unionInfo) = collector.Collect(); - - logger("Method Collect Complete:" + sw.Elapsed.ToString()); - - logger("Output Generation Start"); - sw.Restart(); - - if (Path.GetExtension(output) == ".cs") - { - // SingleFile Output - var fullGeneratedProgramText = GenerateSingleFileSync(resolverName, namespaceDot, objectInfo, enumInfo, unionInfo, genericInfo); - if (multiOutputSymbol == string.Empty) - { - await OutputAsync(output, fullGeneratedProgramText); - } - else - { - var fname = Path.GetFileNameWithoutExtension(output) + "." + MultiSymbolToSafeFilePath(multiOutputSymbol) + ".cs"; - var text = $"#if {multiOutputSymbol}" + Environment.NewLine + fullGeneratedProgramText + Environment.NewLine + "#endif"; - await OutputAsync(Path.Combine(Path.GetDirectoryName(output) ?? string.Empty, fname), text); - } - } - else - { - // Multiple File output - await GenerateMultipleFileAsync(output, resolverName, objectInfo, enumInfo, unionInfo, namespaceDot, multiOutputSymbol, genericInfo); - } - - if (objectInfo.Length == 0 && enumInfo.Length == 0 && genericInfo.Length == 0 && unionInfo.Length == 0) - { - logger("Generated result is empty, unexpected result?"); - } - } - - logger("Output Generation Complete:" + sw.Elapsed.ToString()); - } - - /// - /// Generates the specialized resolver and formatters for the types that require serialization in a given compilation. - /// - /// The resolver name. - /// The namespace for the generated type to be created in. - /// The ObjectSerializationInfo array which TypeCollector.Collect returns. - /// The EnumSerializationInfo array which TypeCollector.Collect returns. - /// The UnionSerializationInfo array which TypeCollector.Collect returns. - /// The GenericSerializationInfo array which TypeCollector.Collect returns. - public static string GenerateSingleFileSync(string resolverName, string namespaceDot, ObjectSerializationInfo[] objectInfo, EnumSerializationInfo[] enumInfo, UnionSerializationInfo[] unionInfo, GenericSerializationInfo[] genericInfo) - { - var objectFormatterTemplates = objectInfo - .GroupBy(x => (x.Namespace, x.IsStringKey)) - .Select(x => - { - var (nameSpace, isStringKey) = x.Key; - var objectSerializationInfos = x.ToArray(); - var ns = namespaceDot + "Formatters" + (nameSpace is null ? string.Empty : "." + nameSpace); - var template = isStringKey ? new StringKeyFormatterTemplate(ns, objectSerializationInfos) : (IFormatterTemplate)new FormatterTemplate(ns, objectSerializationInfos); - return template; - }) - .ToArray(); - - string GetNamespace(IGrouping x) - { - if (x.Key == null) - { - return namespaceDot + "Formatters"; - } - - return namespaceDot + "Formatters." + x.Key; - } - - var enumFormatterTemplates = enumInfo - .GroupBy(x => x.Namespace) - .Select(x => new EnumTemplate(GetNamespace(x), x.ToArray())) - .ToArray(); - - var unionFormatterTemplates = unionInfo - .GroupBy(x => x.Namespace) - .Select(x => new UnionTemplate(GetNamespace(x), x.ToArray())) - .ToArray(); - - var resolverTemplate = new ResolverTemplate(namespaceDot + "Resolvers", namespaceDot + "Formatters", resolverName, genericInfo.Where(x => !x.IsOpenGenericType).Cast().Concat(enumInfo).Concat(unionInfo).Concat(objectInfo.Where(x => !x.IsOpenGenericType)).ToArray()); - - var sb = new StringBuilder(); - sb.AppendLine(resolverTemplate.TransformText()); - sb.AppendLine(); - foreach (var item in enumFormatterTemplates) - { - var text = item.TransformText(); - sb.AppendLine(text); - } - - sb.AppendLine(); - foreach (var item in unionFormatterTemplates) - { - var text = item.TransformText(); - sb.AppendLine(text); - } - - sb.AppendLine(); - foreach (var item in objectFormatterTemplates) - { - var text = item.TransformText(); - sb.AppendLine(text); - } - - return sb.ToString(); - } - - private Task GenerateMultipleFileAsync(string output, string resolverName, ObjectSerializationInfo[] objectInfo, EnumSerializationInfo[] enumInfo, UnionSerializationInfo[] unionInfo, string namespaceDot, string multioutSymbol, GenericSerializationInfo[] genericInfo) - { - string GetNamespace(INamespaceInfo x) - { - if (x.Namespace == null) - { - return namespaceDot + "Formatters"; - } - - return namespaceDot + "Formatters." + x.Namespace; - } - - var waitingTasks = new Task[objectInfo.Length + enumInfo.Length + unionInfo.Length + 1]; - var waitingIndex = 0; - foreach (var x in objectInfo) - { - var ns = namespaceDot + "Formatters" + (x.Namespace is null ? string.Empty : "." + x.Namespace); - var template = x.IsStringKey ? new StringKeyFormatterTemplate(ns, new[] { x }) : (IFormatterTemplate)new FormatterTemplate(ns, new[] { x }); - var text = template.TransformText(); - waitingTasks[waitingIndex++] = OutputToDirAsync(output, template.Namespace, x.Name + "Formatter", multioutSymbol, text); - } - - foreach (var x in enumInfo) - { - var template = new EnumTemplate(GetNamespace(x), new[] { x }); - var text = template.TransformText(); - waitingTasks[waitingIndex++] = OutputToDirAsync(output, template.Namespace, x.Name + "Formatter", multioutSymbol, text); - } - - foreach (var x in unionInfo) - { - var template = new UnionTemplate(GetNamespace(x), new[] { x }); - var text = template.TransformText(); - waitingTasks[waitingIndex++] = OutputToDirAsync(output, template.Namespace, x.Name + "Formatter", multioutSymbol, text); - } - - var resolverTemplate = new ResolverTemplate(namespaceDot + "Resolvers", namespaceDot + "Formatters", resolverName, genericInfo.Where(x => !x.IsOpenGenericType).Cast().Concat(enumInfo).Concat(unionInfo).Concat(objectInfo.Where(x => !x.IsOpenGenericType)).ToArray()); - waitingTasks[waitingIndex] = OutputToDirAsync(output, resolverTemplate.Namespace, resolverTemplate.ResolverName, multioutSymbol, resolverTemplate.TransformText()); - return Task.WhenAll(waitingTasks); - } - - private Task OutputToDirAsync(string dir, string ns, string name, string multipleOutSymbol, string text) - { - var builder = new StringBuilder(); - void AppendDir(string dir) - { - if (dir.Length != 0) - { - builder.Append(dir); - if (dir[dir.Length - 1] != Path.DirectorySeparatorChar && dir[dir.Length - 1] != Path.AltDirectorySeparatorChar) - { - builder.Append(Path.DirectorySeparatorChar); - } - } - } - - void AppendChar(char c) - { - if (c == '.' || InvalidFileCharSet.Contains(c)) - { - builder.Append('_'); - } - else - { - builder.Append(c); - } - } - - void Append(string text) - { - var span = text.AsSpan(); - while (!span.IsEmpty) - { - var index = span.IndexOf("global::".AsSpan()); - if (index == -1) - { - foreach (var c in span) - { - AppendChar(c); - } - - break; - } - - if (index == 0) - { - span = span.Slice("global::".Length); - continue; - } - - foreach (var c in span.Slice(0, index)) - { - AppendChar(c); - } - - span = span.Slice(index + "global::".Length); - } - } - - AppendDir(dir); - - if (!string.IsNullOrWhiteSpace(multipleOutSymbol)) - { - text = $"#if {multipleOutSymbol}" + Environment.NewLine + text + Environment.NewLine + "#endif"; - AppendDir(MultiSymbolToSafeFilePath(multipleOutSymbol)); - } - - Append(ns); - builder.Append('_'); - Append(name); - builder.Append(".cs"); - - return OutputAsync(builder.ToString(), text); - } - - private Task OutputAsync(string path, string text) - { - path = path.Replace("global::", string.Empty); - - const string prefix = "[Out]"; - logger(prefix + path); - - var fi = new FileInfo(path); - if (fi.Directory != null && !fi.Directory.Exists) - { - fi.Directory.Create(); - } - - File.WriteAllText(path, NormalizeNewLines(text), NoBomUtf8); - return Task.CompletedTask; - } - - private static string MultiSymbolToSafeFilePath(string symbol) - { - return symbol.Replace("!", "NOT_").Replace("(", string.Empty).Replace(")", string.Empty).Replace("||", "_OR_").Replace("&&", "_AND_"); - } - - private static string NormalizeNewLines(string content) - { - // The T4 generated code may be text with mixed line ending types. (CR + CRLF) - // We need to normalize the line ending type in each Operating Systems. (e.g. Windows=CRLF, Linux/macOS=LF) - return content.Replace("\r\n", "\n").Replace("\n", Environment.NewLine); - } - } -} diff --git a/src/MessagePack.GeneratorCore/Generator/EnumTemplate.tt b/src/MessagePack.GeneratorCore/Generator/EnumTemplate.tt deleted file mode 100644 index 74214c6c1..000000000 --- a/src/MessagePack.GeneratorCore/Generator/EnumTemplate.tt +++ /dev/null @@ -1,44 +0,0 @@ -<#@ template debug="false" hostspecific="false" linePragmas="false" language="C#" #> -<#@ assembly name="System.Core" #> -<#@ import namespace="System.Linq" #> -<#@ import namespace="System.Text" #> -<#@ import namespace="System.Collections.Generic" #> -// -// THIS (.cs) FILE IS GENERATED BY MPC(MessagePack-CSharp). DO NOT CHANGE IT. -// - -#pragma warning disable 618 -#pragma warning disable 612 -#pragma warning disable 414 -#pragma warning disable 168 -#pragma warning disable CS1591 // document public APIs - -#pragma warning disable SA1403 // File may only contain a single namespace -#pragma warning disable SA1649 // File name should match first type name - -namespace <#= Namespace #> -{ -<# foreach(var info in EnumSerializationInfos) { #> - - public sealed class <#= info.Name #>Formatter : global::MessagePack.Formatters.IMessagePackFormatter<<#= info.FullName #>> - { - public void Serialize(ref global::MessagePack.MessagePackWriter writer, <#= info.FullName #> value, global::MessagePack.MessagePackSerializerOptions options) - { - writer.Write((global::System.<#= info.UnderlyingType #>)value); - } - - public <#= info.FullName #> Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - return (<#= info.FullName #>)reader.Read<#= info.UnderlyingType #>(); - } - } -<# } #> -} - -#pragma warning restore 168 -#pragma warning restore 414 -#pragma warning restore 618 -#pragma warning restore 612 - -#pragma warning restore SA1403 // File may only contain a single namespace -#pragma warning restore SA1649 // File name should match first type name diff --git a/src/MessagePack.GeneratorCore/Generator/FormatterTemplate.cs b/src/MessagePack.GeneratorCore/Generator/FormatterTemplate.cs deleted file mode 100644 index 396d6a52d..000000000 --- a/src/MessagePack.GeneratorCore/Generator/FormatterTemplate.cs +++ /dev/null @@ -1,498 +0,0 @@ -// ------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version: 17.0.0.0 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// ------------------------------------------------------------------------------ -namespace MessagePackCompiler.Generator -{ - using System.Linq; - using System.Text; - using System.Collections.Generic; - using System; - - /// - /// Class to produce the template output - /// - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] - public partial class FormatterTemplate : FormatterTemplateBase - { - /// - /// Create the template output - /// - public virtual string TransformText() - { - this.Write(@"// -// THIS (.cs) FILE IS GENERATED BY MPC(MessagePack-CSharp). DO NOT CHANGE IT. -// - -#pragma warning disable 618 -#pragma warning disable 612 -#pragma warning disable 414 -#pragma warning disable 168 -#pragma warning disable CS1591 // document public APIs - -#pragma warning disable SA1129 // Do not use default value type constructor -#pragma warning disable SA1309 // Field names should not begin with underscore -#pragma warning disable SA1312 // Variable names should begin with lower-case letter -#pragma warning disable SA1403 // File may only contain a single namespace -#pragma warning disable SA1649 // File name should match first type name - -namespace "); - this.Write(this.ToStringHelper.ToStringWithCulture(Namespace)); - this.Write("\r\n{\r\n"); - foreach (var objInfo in ObjectSerializationInfos) { - bool isFormatterResolverNecessary = ShouldUseFormatterResolverHelper.ShouldUseFormatterResolver(objInfo.Members); - this.Write(" public sealed class "); - this.Write(this.ToStringHelper.ToStringWithCulture(objInfo.FormatterNameWithoutNameSpace)); - this.Write(" : global::MessagePack.Formatters.IMessagePackFormatter<"); - this.Write(this.ToStringHelper.ToStringWithCulture(objInfo.FullName)); - this.Write(">\r\n"); - foreach (var typeArg in objInfo.GenericTypeParameters.Where(x => x.HasConstraints)) { - this.Write(" where "); - this.Write(this.ToStringHelper.ToStringWithCulture(typeArg.Name)); - this.Write(" : "); - this.Write(this.ToStringHelper.ToStringWithCulture(typeArg.Constraints)); - this.Write("\r\n"); - } - this.Write(" {\r\n"); - foreach (var item in objInfo.Members) { - if (item.CustomFormatterTypeName != null) { - this.Write(" private readonly "); - this.Write(this.ToStringHelper.ToStringWithCulture(item.CustomFormatterTypeName)); - this.Write(" __"); - this.Write(this.ToStringHelper.ToStringWithCulture(item.Name)); - this.Write("CustomFormatter__ = new "); - this.Write(this.ToStringHelper.ToStringWithCulture(item.CustomFormatterTypeName)); - this.Write("();\r\n"); - } - } - this.Write("\r\n public void Serialize(ref global::MessagePack.MessagePackWriter writer," + - " "); - this.Write(this.ToStringHelper.ToStringWithCulture(objInfo.FullName)); - this.Write(" value, global::MessagePack.MessagePackSerializerOptions options)\r\n {\r\n"); - if (objInfo.IsClass) { - this.Write(" if (value == null)\r\n {\r\n writer.WriteNil();" + - "\r\n return;\r\n }\r\n\r\n"); - } - - if (isFormatterResolverNecessary) { - this.Write(" global::MessagePack.IFormatterResolver formatterResolver = options.Re" + - "solver;\r\n"); - } - - if (objInfo.HasIMessagePackSerializationCallbackReceiver) { - if (objInfo.NeedsCastOnBefore) { - this.Write(" ((global::MessagePack.IMessagePackSerializationCallbackReceiver)value" + - ").OnBeforeSerialize();\r\n"); - } else { - this.Write(" value.OnBeforeSerialize();\r\n"); - } - } - this.Write(" writer.WriteArrayHeader("); - this.Write(this.ToStringHelper.ToStringWithCulture(objInfo.MaxKey + 1)); - this.Write(");\r\n"); - for (var i = 0; i <= objInfo.MaxKey; i++) { - var member = objInfo.GetMember(i); - if (member == null) { - this.Write(" writer.WriteNil();\r\n"); - } else { - this.Write(" "); - this.Write(this.ToStringHelper.ToStringWithCulture(member.GetSerializeMethodString())); - this.Write(";\r\n"); - } - } - this.Write(" }\r\n\r\n public "); - this.Write(this.ToStringHelper.ToStringWithCulture(objInfo.FullName)); - this.Write(" Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePac" + - "k.MessagePackSerializerOptions options)\r\n {\r\n if (reader.TryRe" + - "adNil())\r\n {\r\n"); - if (objInfo.IsClass) { - this.Write(" return null;\r\n"); - } else { - this.Write(" throw new global::System.InvalidOperationException(\"typecode is n" + - "ull, struct not supported\");\r\n"); - } - this.Write(" }\r\n\r\n"); - if (objInfo.MaxKey == -1 && !objInfo.HasIMessagePackSerializationCallbackReceiver) { - this.Write(" reader.Skip();\r\n return new "); - this.Write(this.ToStringHelper.ToStringWithCulture(objInfo.GetConstructorString())); - this.Write(";\r\n"); - } else { - this.Write(" options.Security.DepthStep(ref reader);\r\n"); - if (isFormatterResolverNecessary) { - this.Write(" global::MessagePack.IFormatterResolver formatterResolver = options.Re" + - "solver;\r\n"); - } - this.Write(" var length = reader.ReadArrayHeader();\r\n"); - var canOverwrite = objInfo.ConstructorParameters.Length == 0; - if (canOverwrite) { - this.Write(" var ____result = new "); - this.Write(this.ToStringHelper.ToStringWithCulture(objInfo.GetConstructorString())); - this.Write(";\r\n"); - } else { foreach (var member in objInfo.Members) { - this.Write(" var __"); - this.Write(this.ToStringHelper.ToStringWithCulture(member.Name)); - this.Write("__ = default("); - this.Write(this.ToStringHelper.ToStringWithCulture(member.Type)); - this.Write(");\r\n"); - } - } - this.Write("\r\n for (int i = 0; i < length; i++)\r\n {\r\n sw" + - "itch (i)\r\n {\r\n"); - for (var memberIndex = 0; memberIndex <= objInfo.MaxKey; memberIndex++) { - var member = objInfo.GetMember(memberIndex); - if (member == null) { continue; } - this.Write(" case "); - this.Write(this.ToStringHelper.ToStringWithCulture(member.IntKey)); - this.Write(":\r\n"); - if (canOverwrite) { - if (member.IsWritable) { - this.Write(" ____result."); - this.Write(this.ToStringHelper.ToStringWithCulture(member.Name)); - this.Write(" = "); - this.Write(this.ToStringHelper.ToStringWithCulture(member.GetDeserializeMethodString())); - this.Write(";\r\n"); - } else { - this.Write(" "); - this.Write(this.ToStringHelper.ToStringWithCulture(member.GetDeserializeMethodString())); - this.Write(";\r\n"); - } - } else { - this.Write(" __"); - this.Write(this.ToStringHelper.ToStringWithCulture(member.Name)); - this.Write("__ = "); - this.Write(this.ToStringHelper.ToStringWithCulture(member.GetDeserializeMethodString())); - this.Write(";\r\n"); - } - this.Write(" break;\r\n"); - } - this.Write(" default:\r\n reader.Skip();\r\n " + - " break;\r\n }\r\n }\r\n\r\n"); - if (!canOverwrite) { - this.Write(" var ____result = new "); - this.Write(this.ToStringHelper.ToStringWithCulture(objInfo.GetConstructorString())); - this.Write(";\r\n"); - bool memberAssignExists = false; - for (var memberIndex = 0; memberIndex <= objInfo.MaxKey; memberIndex++) { - var member = objInfo.GetMember(memberIndex); - if (member == null || !member.IsWritable || objInfo.ConstructorParameters.Any(p => p.Equals(member))) { continue; } - memberAssignExists = true; - this.Write(" if (length <= "); - this.Write(this.ToStringHelper.ToStringWithCulture(memberIndex)); - this.Write(")\r\n {\r\n goto MEMBER_ASSIGNMENT_END;\r\n }\r\n\r\n " + - " ____result."); - this.Write(this.ToStringHelper.ToStringWithCulture(member.Name)); - this.Write(" = __"); - this.Write(this.ToStringHelper.ToStringWithCulture(member.Name)); - this.Write("__;\r\n"); - } - if (memberAssignExists) { - this.Write("\r\n MEMBER_ASSIGNMENT_END:\r\n"); - } - } - - if (objInfo.HasIMessagePackSerializationCallbackReceiver) { - if (objInfo.NeedsCastOnAfter) { - this.Write(" ((global::MessagePack.IMessagePackSerializationCallbackReceiver)____r" + - "esult).OnAfterDeserialize();\r\n"); - } else { - this.Write(" ____result.OnAfterDeserialize();\r\n"); - } - } - this.Write(" reader.Depth--;\r\n return ____result;\r\n"); - } - this.Write(" }\r\n }\r\n\r\n"); - } - this.Write(@"} - -#pragma warning restore 168 -#pragma warning restore 414 -#pragma warning restore 618 -#pragma warning restore 612 - -#pragma warning restore SA1129 // Do not use default value type constructor -#pragma warning restore SA1309 // Field names should not begin with underscore -#pragma warning restore SA1312 // Variable names should begin with lower-case letter -#pragma warning restore SA1403 // File may only contain a single namespace -#pragma warning restore SA1649 // File name should match first type name -"); - return this.GenerationEnvironment.ToString(); - } - } - #region Base class - /// - /// Base class for this transformation - /// - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] - public class FormatterTemplateBase - { - #region Fields - private global::System.Text.StringBuilder generationEnvironmentField; - private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; - private global::System.Collections.Generic.List indentLengthsField; - private string currentIndentField = ""; - private bool endsWithNewline; - private global::System.Collections.Generic.IDictionary sessionField; - #endregion - #region Properties - /// - /// The string builder that generation-time code is using to assemble generated output - /// - protected System.Text.StringBuilder GenerationEnvironment - { - get - { - if ((this.generationEnvironmentField == null)) - { - this.generationEnvironmentField = new global::System.Text.StringBuilder(); - } - return this.generationEnvironmentField; - } - set - { - this.generationEnvironmentField = value; - } - } - /// - /// The error collection for the generation process - /// - public System.CodeDom.Compiler.CompilerErrorCollection Errors - { - get - { - if ((this.errorsField == null)) - { - this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); - } - return this.errorsField; - } - } - /// - /// A list of the lengths of each indent that was added with PushIndent - /// - private System.Collections.Generic.List indentLengths - { - get - { - if ((this.indentLengthsField == null)) - { - this.indentLengthsField = new global::System.Collections.Generic.List(); - } - return this.indentLengthsField; - } - } - /// - /// Gets the current indent we use when adding lines to the output - /// - public string CurrentIndent - { - get - { - return this.currentIndentField; - } - } - /// - /// Current transformation session - /// - public virtual global::System.Collections.Generic.IDictionary Session - { - get - { - return this.sessionField; - } - set - { - this.sessionField = value; - } - } - #endregion - #region Transform-time helpers - /// - /// Write text directly into the generated output - /// - public void Write(string textToAppend) - { - if (string.IsNullOrEmpty(textToAppend)) - { - return; - } - // If we're starting off, or if the previous text ended with a newline, - // we have to append the current indent first. - if (((this.GenerationEnvironment.Length == 0) - || this.endsWithNewline)) - { - this.GenerationEnvironment.Append(this.currentIndentField); - this.endsWithNewline = false; - } - // Check if the current text ends with a newline - if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) - { - this.endsWithNewline = true; - } - // This is an optimization. If the current indent is "", then we don't have to do any - // of the more complex stuff further down. - if ((this.currentIndentField.Length == 0)) - { - this.GenerationEnvironment.Append(textToAppend); - return; - } - // Everywhere there is a newline in the text, add an indent after it - textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); - // If the text ends with a newline, then we should strip off the indent added at the very end - // because the appropriate indent will be added when the next time Write() is called - if (this.endsWithNewline) - { - this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); - } - else - { - this.GenerationEnvironment.Append(textToAppend); - } - } - /// - /// Write text directly into the generated output - /// - public void WriteLine(string textToAppend) - { - this.Write(textToAppend); - this.GenerationEnvironment.AppendLine(); - this.endsWithNewline = true; - } - /// - /// Write formatted text directly into the generated output - /// - public void Write(string format, params object[] args) - { - this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); - } - /// - /// Write formatted text directly into the generated output - /// - public void WriteLine(string format, params object[] args) - { - this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); - } - /// - /// Raise an error - /// - public void Error(string message) - { - System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); - error.ErrorText = message; - this.Errors.Add(error); - } - /// - /// Raise a warning - /// - public void Warning(string message) - { - System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); - error.ErrorText = message; - error.IsWarning = true; - this.Errors.Add(error); - } - /// - /// Increase the indent - /// - public void PushIndent(string indent) - { - if ((indent == null)) - { - throw new global::System.ArgumentNullException("indent"); - } - this.currentIndentField = (this.currentIndentField + indent); - this.indentLengths.Add(indent.Length); - } - /// - /// Remove the last indent that was added with PushIndent - /// - public string PopIndent() - { - string returnValue = ""; - if ((this.indentLengths.Count > 0)) - { - int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; - this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); - if ((indentLength > 0)) - { - returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); - this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); - } - } - return returnValue; - } - /// - /// Remove any indentation - /// - public void ClearIndent() - { - this.indentLengths.Clear(); - this.currentIndentField = ""; - } - #endregion - #region ToString Helpers - /// - /// Utility class to produce culture-oriented representation of an object as a string. - /// - public class ToStringInstanceHelper - { - private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; - /// - /// Gets or sets format provider to be used by ToStringWithCulture method. - /// - public System.IFormatProvider FormatProvider - { - get - { - return this.formatProviderField ; - } - set - { - if ((value != null)) - { - this.formatProviderField = value; - } - } - } - /// - /// This is called from the compile/run appdomain to convert objects within an expression block to a string - /// - public string ToStringWithCulture(object objectToConvert) - { - if ((objectToConvert == null)) - { - throw new global::System.ArgumentNullException("objectToConvert"); - } - System.Type t = objectToConvert.GetType(); - System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { - typeof(System.IFormatProvider)}); - if ((method == null)) - { - return objectToConvert.ToString(); - } - else - { - return ((string)(method.Invoke(objectToConvert, new object[] { - this.formatProviderField }))); - } - } - } - private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); - /// - /// Helper to produce culture-oriented representation of an object as a string - /// - public ToStringInstanceHelper ToStringHelper - { - get - { - return this.toStringHelperField; - } - } - #endregion - } - #endregion -} diff --git a/src/MessagePack.GeneratorCore/Generator/FormatterTemplate.tt b/src/MessagePack.GeneratorCore/Generator/FormatterTemplate.tt deleted file mode 100644 index 240b107fc..000000000 --- a/src/MessagePack.GeneratorCore/Generator/FormatterTemplate.tt +++ /dev/null @@ -1,167 +0,0 @@ -<#@ template debug="false" hostspecific="false" linePragmas="false" language="C#" #> -<#@ assembly name="System.Core" #> -<#@ import namespace="System.Linq" #> -<#@ import namespace="System.Text" #> -<#@ import namespace="System.Collections.Generic" #> -// -// THIS (.cs) FILE IS GENERATED BY MPC(MessagePack-CSharp). DO NOT CHANGE IT. -// - -#pragma warning disable 618 -#pragma warning disable 612 -#pragma warning disable 414 -#pragma warning disable 168 -#pragma warning disable CS1591 // document public APIs - -#pragma warning disable SA1129 // Do not use default value type constructor -#pragma warning disable SA1309 // Field names should not begin with underscore -#pragma warning disable SA1312 // Variable names should begin with lower-case letter -#pragma warning disable SA1403 // File may only contain a single namespace -#pragma warning disable SA1649 // File name should match first type name - -namespace <#= Namespace #> -{ -<# foreach (var objInfo in ObjectSerializationInfos) { - bool isFormatterResolverNecessary = ShouldUseFormatterResolverHelper.ShouldUseFormatterResolver(objInfo.Members);#> - public sealed class <#= objInfo.FormatterNameWithoutNameSpace #> : global::MessagePack.Formatters.IMessagePackFormatter<<#= objInfo.FullName #>> -<# foreach (var typeArg in objInfo.GenericTypeParameters.Where(x => x.HasConstraints)) { #> - where <#= typeArg.Name #> : <#= typeArg.Constraints #> -<# } #> - { -<# foreach (var item in objInfo.Members) { #> -<# if (item.CustomFormatterTypeName != null) { #> - private readonly <#= item.CustomFormatterTypeName #> __<#= item.Name #>CustomFormatter__ = new <#= item.CustomFormatterTypeName #>(); -<# } #> -<# } #> - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, <#= objInfo.FullName #> value, global::MessagePack.MessagePackSerializerOptions options) - { -<# if (objInfo.IsClass) { #> - if (value == null) - { - writer.WriteNil(); - return; - } - -<# } - - if (isFormatterResolverNecessary) { #> - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; -<# } - - if (objInfo.HasIMessagePackSerializationCallbackReceiver) { - if (objInfo.NeedsCastOnBefore) { #> - ((global::MessagePack.IMessagePackSerializationCallbackReceiver)value).OnBeforeSerialize(); -<# } else { #> - value.OnBeforeSerialize(); -<# } #> -<# } #> - writer.WriteArrayHeader(<#= objInfo.MaxKey + 1 #>); -<# for (var i = 0; i <= objInfo.MaxKey; i++) { - var member = objInfo.GetMember(i); - if (member == null) { #> - writer.WriteNil(); -<# } else { #> - <#= member.GetSerializeMethodString() #>; -<# } #> -<# } #> - } - - public <#= objInfo.FullName #> Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { -<# if (objInfo.IsClass) { #> - return null; -<# } else { #> - throw new global::System.InvalidOperationException("typecode is null, struct not supported"); -<# } #> - } - -<# if (objInfo.MaxKey == -1 && !objInfo.HasIMessagePackSerializationCallbackReceiver) { #> - reader.Skip(); - return new <#= objInfo.GetConstructorString() #>; -<# } else { #> - options.Security.DepthStep(ref reader); -<# if (isFormatterResolverNecessary) { #> - global::MessagePack.IFormatterResolver formatterResolver = options.Resolver; -<# } #> - var length = reader.ReadArrayHeader(); -<# var canOverwrite = objInfo.ConstructorParameters.Length == 0; - if (canOverwrite) { #> - var ____result = new <#= objInfo.GetConstructorString() #>; -<# } else { foreach (var member in objInfo.Members) { #> - var __<#= member.Name #>__ = default(<#= member.Type #>); -<# } #> -<# } #> - - for (int i = 0; i < length; i++) - { - switch (i) - { -<# for (var memberIndex = 0; memberIndex <= objInfo.MaxKey; memberIndex++) { - var member = objInfo.GetMember(memberIndex); - if (member == null) { continue; } #> - case <#= member.IntKey #>: -<# if (canOverwrite) { - if (member.IsWritable) { #> - ____result.<#= member.Name #> = <#= member.GetDeserializeMethodString() #>; -<# } else { #> - <#= member.GetDeserializeMethodString() #>; -<# } #> -<# } else {#> - __<#= member.Name #>__ = <#= member.GetDeserializeMethodString() #>; -<# } #> - break; -<# } #> - default: - reader.Skip(); - break; - } - } - -<# if (!canOverwrite) { #> - var ____result = new <#= objInfo.GetConstructorString() #>; -<# bool memberAssignExists = false; - for (var memberIndex = 0; memberIndex <= objInfo.MaxKey; memberIndex++) { - var member = objInfo.GetMember(memberIndex); - if (member == null || !member.IsWritable || objInfo.ConstructorParameters.Any(p => p.Equals(member))) { continue; } - memberAssignExists = true;#> - if (length <= <#= memberIndex #>) - { - goto MEMBER_ASSIGNMENT_END; - } - - ____result.<#= member.Name #> = __<#= member.Name #>__; -<# } #> -<# if (memberAssignExists) { #> - - MEMBER_ASSIGNMENT_END: -<# } - } - - if (objInfo.HasIMessagePackSerializationCallbackReceiver) { - if (objInfo.NeedsCastOnAfter) { #> - ((global::MessagePack.IMessagePackSerializationCallbackReceiver)____result).OnAfterDeserialize(); -<# } else { #> - ____result.OnAfterDeserialize(); -<# } #> -<# } #> - reader.Depth--; - return ____result; -<# } #> - } - } - -<# } #>} - -#pragma warning restore 168 -#pragma warning restore 414 -#pragma warning restore 618 -#pragma warning restore 612 - -#pragma warning restore SA1129 // Do not use default value type constructor -#pragma warning restore SA1309 // Field names should not begin with underscore -#pragma warning restore SA1312 // Variable names should begin with lower-case letter -#pragma warning restore SA1403 // File may only contain a single namespace -#pragma warning restore SA1649 // File name should match first type name diff --git a/src/MessagePack.GeneratorCore/Generator/IFormatterTemplate.cs b/src/MessagePack.GeneratorCore/Generator/IFormatterTemplate.cs deleted file mode 100644 index ea3363a6e..000000000 --- a/src/MessagePack.GeneratorCore/Generator/IFormatterTemplate.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) All contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using MessagePackCompiler.CodeAnalysis; - -namespace MessagePackCompiler.Generator -{ - public interface IFormatterTemplate - { - string Namespace { get; } - - ObjectSerializationInfo[] ObjectSerializationInfos { get; } - - string TransformText(); - } -} diff --git a/src/MessagePack.GeneratorCore/Generator/ResolverTemplate.cs b/src/MessagePack.GeneratorCore/Generator/ResolverTemplate.cs deleted file mode 100644 index 58f207114..000000000 --- a/src/MessagePack.GeneratorCore/Generator/ResolverTemplate.cs +++ /dev/null @@ -1,402 +0,0 @@ -// ------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version: 17.0.0.0 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// ------------------------------------------------------------------------------ -namespace MessagePackCompiler.Generator -{ - using System.Linq; - using System.Text; - using System.Collections.Generic; - using System; - - /// - /// Class to produce the template output - /// - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] - public partial class ResolverTemplate : ResolverTemplateBase - { - /// - /// Create the template output - /// - public virtual string TransformText() - { - this.Write(@"// -// THIS (.cs) FILE IS GENERATED BY MPC(MessagePack-CSharp). DO NOT CHANGE IT. -// - -#pragma warning disable 618 -#pragma warning disable 612 -#pragma warning disable 414 -#pragma warning disable 168 -#pragma warning disable CS1591 // document public APIs - -#pragma warning disable SA1312 // Variable names should begin with lower-case letter -#pragma warning disable SA1649 // File name should match first type name - -namespace "); - this.Write(this.ToStringHelper.ToStringWithCulture(Namespace)); - this.Write("\r\n{\r\n public class "); - this.Write(this.ToStringHelper.ToStringWithCulture(ResolverName)); - this.Write(" : global::MessagePack.IFormatterResolver\r\n {\r\n public static readonly " + - "global::MessagePack.IFormatterResolver Instance = new "); - this.Write(this.ToStringHelper.ToStringWithCulture(ResolverName)); - this.Write("();\r\n\r\n private "); - this.Write(this.ToStringHelper.ToStringWithCulture(ResolverName)); - this.Write(@"() - { - } - - public global::MessagePack.Formatters.IMessagePackFormatter GetFormatter() - { - return FormatterCache.Formatter; - } - - private static class FormatterCache - { - internal static readonly global::MessagePack.Formatters.IMessagePackFormatter Formatter; - - static FormatterCache() - { - var f = "); - this.Write(this.ToStringHelper.ToStringWithCulture(ResolverName)); - this.Write(@"GetFormatterHelper.GetFormatter(typeof(T)); - if (f != null) - { - Formatter = (global::MessagePack.Formatters.IMessagePackFormatter)f; - } - } - } - } - - internal static class "); - this.Write(this.ToStringHelper.ToStringWithCulture(ResolverName)); - this.Write("GetFormatterHelper\r\n {\r\n private static readonly global::System.Collect" + - "ions.Generic.Dictionary lookup;\r\n\r\n static "); - this.Write(this.ToStringHelper.ToStringWithCulture(ResolverName)); - this.Write("GetFormatterHelper()\r\n {\r\n lookup = new global::System.Collecti" + - "ons.Generic.Dictionary("); - this.Write(this.ToStringHelper.ToStringWithCulture(RegisterInfos.Length)); - this.Write(")\r\n {\r\n"); - for(var i = 0; i < RegisterInfos.Length; i++) { var x = RegisterInfos[i]; - this.Write(" { typeof("); - this.Write(this.ToStringHelper.ToStringWithCulture(x.FullName)); - this.Write("), "); - this.Write(this.ToStringHelper.ToStringWithCulture(i)); - this.Write(" },\r\n"); - } - this.Write(@" }; - } - - internal static object GetFormatter(global::System.Type t) - { - int key; - if (!lookup.TryGetValue(t, out key)) - { - return null; - } - - switch (key) - { -"); - for(var i = 0; i < RegisterInfos.Length; i++) { var x = RegisterInfos[i]; - this.Write(" case "); - this.Write(this.ToStringHelper.ToStringWithCulture(i)); - this.Write(": return new "); - this.Write(this.ToStringHelper.ToStringWithCulture(x.FormatterName.StartsWith("global::") ? x.FormatterName: (!string.IsNullOrEmpty(FormatterNamespace) ? FormatterNamespace + "." : FormatterNamespace) + x.FormatterName)); - this.Write("();\r\n"); - } - this.Write(@" default: return null; - } - } - } -} - -#pragma warning restore 168 -#pragma warning restore 414 -#pragma warning restore 618 -#pragma warning restore 612 - -#pragma warning restore SA1312 // Variable names should begin with lower-case letter -#pragma warning restore SA1649 // File name should match first type name -"); - return this.GenerationEnvironment.ToString(); - } - } - #region Base class - /// - /// Base class for this transformation - /// - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] - public class ResolverTemplateBase - { - #region Fields - private global::System.Text.StringBuilder generationEnvironmentField; - private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; - private global::System.Collections.Generic.List indentLengthsField; - private string currentIndentField = ""; - private bool endsWithNewline; - private global::System.Collections.Generic.IDictionary sessionField; - #endregion - #region Properties - /// - /// The string builder that generation-time code is using to assemble generated output - /// - protected System.Text.StringBuilder GenerationEnvironment - { - get - { - if ((this.generationEnvironmentField == null)) - { - this.generationEnvironmentField = new global::System.Text.StringBuilder(); - } - return this.generationEnvironmentField; - } - set - { - this.generationEnvironmentField = value; - } - } - /// - /// The error collection for the generation process - /// - public System.CodeDom.Compiler.CompilerErrorCollection Errors - { - get - { - if ((this.errorsField == null)) - { - this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); - } - return this.errorsField; - } - } - /// - /// A list of the lengths of each indent that was added with PushIndent - /// - private System.Collections.Generic.List indentLengths - { - get - { - if ((this.indentLengthsField == null)) - { - this.indentLengthsField = new global::System.Collections.Generic.List(); - } - return this.indentLengthsField; - } - } - /// - /// Gets the current indent we use when adding lines to the output - /// - public string CurrentIndent - { - get - { - return this.currentIndentField; - } - } - /// - /// Current transformation session - /// - public virtual global::System.Collections.Generic.IDictionary Session - { - get - { - return this.sessionField; - } - set - { - this.sessionField = value; - } - } - #endregion - #region Transform-time helpers - /// - /// Write text directly into the generated output - /// - public void Write(string textToAppend) - { - if (string.IsNullOrEmpty(textToAppend)) - { - return; - } - // If we're starting off, or if the previous text ended with a newline, - // we have to append the current indent first. - if (((this.GenerationEnvironment.Length == 0) - || this.endsWithNewline)) - { - this.GenerationEnvironment.Append(this.currentIndentField); - this.endsWithNewline = false; - } - // Check if the current text ends with a newline - if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) - { - this.endsWithNewline = true; - } - // This is an optimization. If the current indent is "", then we don't have to do any - // of the more complex stuff further down. - if ((this.currentIndentField.Length == 0)) - { - this.GenerationEnvironment.Append(textToAppend); - return; - } - // Everywhere there is a newline in the text, add an indent after it - textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); - // If the text ends with a newline, then we should strip off the indent added at the very end - // because the appropriate indent will be added when the next time Write() is called - if (this.endsWithNewline) - { - this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); - } - else - { - this.GenerationEnvironment.Append(textToAppend); - } - } - /// - /// Write text directly into the generated output - /// - public void WriteLine(string textToAppend) - { - this.Write(textToAppend); - this.GenerationEnvironment.AppendLine(); - this.endsWithNewline = true; - } - /// - /// Write formatted text directly into the generated output - /// - public void Write(string format, params object[] args) - { - this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); - } - /// - /// Write formatted text directly into the generated output - /// - public void WriteLine(string format, params object[] args) - { - this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); - } - /// - /// Raise an error - /// - public void Error(string message) - { - System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); - error.ErrorText = message; - this.Errors.Add(error); - } - /// - /// Raise a warning - /// - public void Warning(string message) - { - System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); - error.ErrorText = message; - error.IsWarning = true; - this.Errors.Add(error); - } - /// - /// Increase the indent - /// - public void PushIndent(string indent) - { - if ((indent == null)) - { - throw new global::System.ArgumentNullException("indent"); - } - this.currentIndentField = (this.currentIndentField + indent); - this.indentLengths.Add(indent.Length); - } - /// - /// Remove the last indent that was added with PushIndent - /// - public string PopIndent() - { - string returnValue = ""; - if ((this.indentLengths.Count > 0)) - { - int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; - this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); - if ((indentLength > 0)) - { - returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); - this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); - } - } - return returnValue; - } - /// - /// Remove any indentation - /// - public void ClearIndent() - { - this.indentLengths.Clear(); - this.currentIndentField = ""; - } - #endregion - #region ToString Helpers - /// - /// Utility class to produce culture-oriented representation of an object as a string. - /// - public class ToStringInstanceHelper - { - private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; - /// - /// Gets or sets format provider to be used by ToStringWithCulture method. - /// - public System.IFormatProvider FormatProvider - { - get - { - return this.formatProviderField ; - } - set - { - if ((value != null)) - { - this.formatProviderField = value; - } - } - } - /// - /// This is called from the compile/run appdomain to convert objects within an expression block to a string - /// - public string ToStringWithCulture(object objectToConvert) - { - if ((objectToConvert == null)) - { - throw new global::System.ArgumentNullException("objectToConvert"); - } - System.Type t = objectToConvert.GetType(); - System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { - typeof(System.IFormatProvider)}); - if ((method == null)) - { - return objectToConvert.ToString(); - } - else - { - return ((string)(method.Invoke(objectToConvert, new object[] { - this.formatProviderField }))); - } - } - } - private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); - /// - /// Helper to produce culture-oriented representation of an object as a string - /// - public ToStringInstanceHelper ToStringHelper - { - get - { - return this.toStringHelperField; - } - } - #endregion - } - #endregion -} diff --git a/src/MessagePack.GeneratorCore/Generator/ResolverTemplate.tt b/src/MessagePack.GeneratorCore/Generator/ResolverTemplate.tt deleted file mode 100644 index 6837185df..000000000 --- a/src/MessagePack.GeneratorCore/Generator/ResolverTemplate.tt +++ /dev/null @@ -1,88 +0,0 @@ -<#@ template debug="false" hostspecific="false" linePragmas="false" language="C#" #> -<#@ assembly name="System.Core" #> -<#@ import namespace="System.Linq" #> -<#@ import namespace="System.Text" #> -<#@ import namespace="System.Collections.Generic" #> -// -// THIS (.cs) FILE IS GENERATED BY MPC(MessagePack-CSharp). DO NOT CHANGE IT. -// - -#pragma warning disable 618 -#pragma warning disable 612 -#pragma warning disable 414 -#pragma warning disable 168 -#pragma warning disable CS1591 // document public APIs - -#pragma warning disable SA1312 // Variable names should begin with lower-case letter -#pragma warning disable SA1649 // File name should match first type name - -namespace <#= Namespace #> -{ - public class <#= ResolverName #> : global::MessagePack.IFormatterResolver - { - public static readonly global::MessagePack.IFormatterResolver Instance = new <#= ResolverName #>(); - - private <#= ResolverName #>() - { - } - - public global::MessagePack.Formatters.IMessagePackFormatter GetFormatter() - { - return FormatterCache.Formatter; - } - - private static class FormatterCache - { - internal static readonly global::MessagePack.Formatters.IMessagePackFormatter Formatter; - - static FormatterCache() - { - var f = <#= ResolverName #>GetFormatterHelper.GetFormatter(typeof(T)); - if (f != null) - { - Formatter = (global::MessagePack.Formatters.IMessagePackFormatter)f; - } - } - } - } - - internal static class <#= ResolverName #>GetFormatterHelper - { - private static readonly global::System.Collections.Generic.Dictionary lookup; - - static <#= ResolverName #>GetFormatterHelper() - { - lookup = new global::System.Collections.Generic.Dictionary(<#= RegisterInfos.Length #>) - { -<# for(var i = 0; i < RegisterInfos.Length; i++) { var x = RegisterInfos[i]; #> - { typeof(<#= x.FullName #>), <#= i #> }, -<# } #> - }; - } - - internal static object GetFormatter(global::System.Type t) - { - int key; - if (!lookup.TryGetValue(t, out key)) - { - return null; - } - - switch (key) - { -<# for(var i = 0; i < RegisterInfos.Length; i++) { var x = RegisterInfos[i]; #> - case <#= i #>: return new <#= x.FormatterName.StartsWith("global::") ? x.FormatterName: (!string.IsNullOrEmpty(FormatterNamespace) ? FormatterNamespace + "." : FormatterNamespace) + x.FormatterName #>(); -<# } #> - default: return null; - } - } - } -} - -#pragma warning restore 168 -#pragma warning restore 414 -#pragma warning restore 618 -#pragma warning restore 612 - -#pragma warning restore SA1312 // Variable names should begin with lower-case letter -#pragma warning restore SA1649 // File name should match first type name diff --git a/src/MessagePack.GeneratorCore/Generator/ShouldUseFormatterResolverHelper.cs b/src/MessagePack.GeneratorCore/Generator/ShouldUseFormatterResolverHelper.cs deleted file mode 100644 index ab5e61866..000000000 --- a/src/MessagePack.GeneratorCore/Generator/ShouldUseFormatterResolverHelper.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) All contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using MessagePackCompiler.CodeAnalysis; - -namespace MessagePackCompiler.Generator -{ - public static class ShouldUseFormatterResolverHelper - { - /// - /// Keep this list in sync with DynamicObjectTypeBuilder.IsOptimizeTargetType. - /// - internal static readonly string[] PrimitiveTypes = - { - "short", - "int", - "long", - "ushort", - "uint", - "ulong", - "float", - "double", - "bool", - "byte", - "sbyte", - "char", - "byte[]", - - // Do not include types that resolvers are allowed to modify. - ////"global::System.DateTime", // OldSpec has no support, so for that and perf reasons a .NET native DateTime resolver exists. - ////"string", // https://github.com/Cysharp/MasterMemory provides custom formatter for string interning. - }; - - public static bool ShouldUseFormatterResolver(MemberSerializationInfo[] infos) - { - foreach (var memberSerializationInfo in infos) - { - if (memberSerializationInfo.CustomFormatterTypeName == null && Array.IndexOf(PrimitiveTypes, memberSerializationInfo.Type) == -1) - { - return true; - } - } - - return false; - } - } -} diff --git a/src/MessagePack.GeneratorCore/Generator/StringKey/EmbedStringHelper.cs b/src/MessagePack.GeneratorCore/Generator/StringKey/EmbedStringHelper.cs deleted file mode 100644 index f10e26087..000000000 --- a/src/MessagePack.GeneratorCore/Generator/StringKey/EmbedStringHelper.cs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) All contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Text; - -namespace MessagePackCompiler.Generator -{ - public static class EmbedStringHelper - { - public static readonly Encoding Utf8 = new UTF8Encoding(false); - - public static string ToByteArrayString(byte[] binary) - { - var headerLength = GetHeaderLength(binary.Length); - Span header = stackalloc byte[headerLength]; - EmbedHeader(binary.Length, header); - var buffer = new StringBuilder().Append("new byte[").Append(headerLength).Append(" + ").Append(binary.Length).Append("] { ").Append(header[0]); - foreach (var b in header.Slice(1)) - { - buffer.Append(", ").Append(b); - } - - foreach (var b in binary) - { - buffer.Append(", ").Append(b); - } - - return buffer.Append(" }").ToString(); - } - - public static int GetHeaderLength(int byteCount) - { - if (byteCount <= 31) - { - return 1; - } - - if (byteCount <= byte.MaxValue) - { - return 2; - } - - return byteCount <= ushort.MaxValue ? 3 : 5; - } - - public static void EmbedHeader(int byteCount, Span destination) - { - if (byteCount <= 31) - { - destination[0] = (byte)(0xa0 | byteCount); - return; - } - - if (byteCount <= byte.MaxValue) - { - destination[0] = 0xd9; - destination[1] = unchecked((byte)byteCount); - return; - } - - if (byteCount <= ushort.MaxValue) - { - destination[0] = 0xda; - destination[1] = unchecked((byte)(byteCount >> 8)); - destination[2] = unchecked((byte)byteCount); - return; - } - - destination[0] = 0xdb; - destination[1] = unchecked((byte)(byteCount >> 24)); - destination[2] = unchecked((byte)(byteCount >> 16)); - destination[3] = unchecked((byte)(byteCount >> 8)); - destination[4] = unchecked((byte)byteCount); - } - - public static byte[] GetEncodedStringBytes(string value) - { - var byteCount = Utf8.GetByteCount(value); - var headerLength = GetHeaderLength(byteCount); - var bytes = new byte[headerLength + byteCount]; - EmbedHeader(byteCount, bytes); - Utf8.GetBytes(value, 0, value.Length, bytes, headerLength); - return bytes; - } - } -} diff --git a/src/MessagePack.GeneratorCore/Generator/StringKey/StringKeyFormatterDeserializeHelper.cs b/src/MessagePack.GeneratorCore/Generator/StringKey/StringKeyFormatterDeserializeHelper.cs deleted file mode 100644 index f65bed0b3..000000000 --- a/src/MessagePack.GeneratorCore/Generator/StringKey/StringKeyFormatterDeserializeHelper.cs +++ /dev/null @@ -1,252 +0,0 @@ -// Copyright (c) All contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using MessagePack.Internal; -using MessagePackCompiler.CodeAnalysis; - -namespace MessagePackCompiler.Generator -{ - internal static class StringKeyFormatterDeserializeHelper - { - public static string Classify(ObjectSerializationInfo objectSerializationInfo, string indent, bool canOverwrite) - { - var memberArray = objectSerializationInfo.Members; - var buffer = new StringBuilder(); - foreach (var memberInfoTuples in memberArray.Select(member => new MemberInfoTuple(member, IsConstructorParameter(objectSerializationInfo, member))).GroupBy(member => member.Binary.Length)) - { - var binaryLength = memberInfoTuples.Key; - var keyLength = binaryLength >> 3; - keyLength += keyLength << 3 == binaryLength ? 0 : 1; - - buffer.Append(indent).Append("case ").Append(binaryLength).Append(":\r\n"); - ClassifyRecursion(buffer, indent, 1, keyLength, memberInfoTuples, canOverwrite); - } - - return buffer.ToString(); - } - - private static bool IsConstructorParameter(ObjectSerializationInfo objectSerializationInfo, MemberSerializationInfo member) - { - foreach (var parameter in objectSerializationInfo.ConstructorParameters) - { - if (parameter.Equals(member)) - { - return true; - } - } - - return false; - } - - private static void Assign(StringBuilder buffer, in MemberInfoTuple member, bool canOverwrite, string indent, string tab, int tabCount) - { - if (member.Info.IsWritable || member.IsConstructorParameter) - { - if (canOverwrite) - { - buffer.Append("____result.").Append(member.Info.Name).Append(" = "); - } - else - { - if (!member.IsConstructorParameter) - { - buffer.Append("__").Append(member.Info.Name).Append("__IsInitialized = true;\r\n").Append(indent); - for (var i = 0; i < tabCount; i++) - { - buffer.Append(tab); - } - } - - buffer.Append("__").Append(member.Info.Name).Append("__ = "); - } - - buffer.Append(member.Info.GetDeserializeMethodString()).Append(";\r\n"); - } - else - { - buffer.Append("reader.Skip();\r\n"); - } - } - - private static void ClassifyRecursion(StringBuilder buffer, string indent, int tabCount, int keyLength, IEnumerable memberCollection, bool canOverwrite) - { - const string Tab = " "; - buffer.Append(indent); - for (var i = 0; i < tabCount; i++) - { - buffer.Append(Tab); - } - - var memberArray = memberCollection.ToArray(); - if (memberArray.Length == 1) - { - var member = memberArray[0]; - EmbedOne(buffer, indent, tabCount, member, canOverwrite); - return; - } - - buffer.Append("switch (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey))\r\n").Append(indent); - for (var i = 0; i < tabCount; i++) - { - buffer.Append(Tab); - } - - buffer.Append("{\r\n" + Tab).Append(indent); - for (var i = 0; i < tabCount; i++) - { - buffer.Append(Tab); - } - - buffer.Append("default: goto FAIL;"); - - foreach (var grouping in memberArray.GroupBy(member => member.Key[tabCount - 1])) - { - buffer.Append("\r\n" + Tab).Append(indent); - for (var i = 0; i < tabCount; i++) - { - buffer.Append(Tab); - } - - buffer.Append("case ").Append(grouping.Key).Append("UL:\r\n"); - - if (tabCount == keyLength) - { - buffer.Append(Tab + Tab).Append(indent); - for (var i = 0; i < tabCount; i++) - { - buffer.Append(Tab); - } - - var member = grouping.Single(); - Assign(buffer, member, canOverwrite, indent, Tab, tabCount + 2); - buffer.Append(Tab + Tab).Append(indent); - for (var i = 0; i < tabCount; i++) - { - buffer.Append(Tab); - } - - buffer.Append("continue;"); - continue; - } - - ClassifyRecursion(buffer, indent + Tab, tabCount + 1, keyLength, grouping, canOverwrite); - } - - buffer.Append("\r\n").Append(indent); - for (var i = 0; i < tabCount; i++) - { - buffer.Append(Tab); - } - - buffer.Append("}\r\n"); - } - - private static void EmbedOne(StringBuilder buffer, string indent, int tabCount, in MemberInfoTuple member, bool canOverwrite) - { - const string Tab = " "; - var binary = member.Binary.AsSpan((tabCount - 1) << 3); - - switch (binary.Length) - { - case 1: - buffer.Append("if (stringKey[0] != ").Append(binary[0]); - break; - case 2: - case 3: - case 4: - case 5: - case 6: - case 7: - case 8: - buffer.Append("if (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey) != ").Append(member.Key[tabCount - 1]).Append("UL"); - break; - default: - EmbedSequenceEqual(buffer, member, (tabCount << 3) - 8); - break; - } - - buffer.Append(") { goto FAIL; }\r\n\r\n").Append(indent); - for (var i = 0; i < tabCount; i++) - { - buffer.Append(Tab); - } - - Assign(buffer, member, canOverwrite, indent, Tab, tabCount); - buffer.Append(indent); - for (var i = 0; i < tabCount; i++) - { - buffer.Append(Tab); - } - - buffer.Append("continue;\r\n"); - } - - private static void EmbedSequenceEqual(StringBuilder buffer, MemberInfoTuple member, int startPosition) - { - buffer - .Append("if (!global::System.MemoryExtensions.SequenceEqual(stringKey, GetSpan_") - .Append(member.Info.Name) - .Append("().Slice(") - .Append(EmbedStringHelper.GetHeaderLength(member.Binary.Length)); - - if (startPosition != 0) - { - buffer.Append(" + ").Append(startPosition); - } - - buffer.Append("))"); - } - } - - internal readonly struct MemberInfoTuple : IComparable - { - public readonly MemberSerializationInfo Info; - public readonly bool IsConstructorParameter; - public readonly byte[] Binary; - public readonly ulong[] Key; - - public MemberInfoTuple(MemberSerializationInfo info, bool isConstructorParameter) - { - Info = info; - IsConstructorParameter = isConstructorParameter; - Binary = EmbedStringHelper.Utf8.GetBytes(info.StringKey); - ReadOnlySpan span = Binary; - var keyLength = Binary.Length >> 3; - keyLength += keyLength << 3 == Binary.Length ? 0 : 1; - Key = new ulong[keyLength]; - for (var i = 0; i < Key.Length; i++) - { - Key[i] = AutomataKeyGen.GetKey(ref span); - } - } - - public int CompareTo(MemberInfoTuple other) - { - if (Info == other.Info) - { - return 0; - } - - var c = Binary.Length.CompareTo(other.Binary.Length); - if (c != 0) - { - return c; - } - - for (var i = 0; i < Key.Length; i++) - { - c = Key[i].CompareTo(other.Key[i]); - if (c != 0) - { - return c; - } - } - - return 0; - } - } -} diff --git a/src/MessagePack.GeneratorCore/Generator/StringKey/StringKeyFormatterTemplate.cs b/src/MessagePack.GeneratorCore/Generator/StringKey/StringKeyFormatterTemplate.cs deleted file mode 100644 index 083c69037..000000000 --- a/src/MessagePack.GeneratorCore/Generator/StringKey/StringKeyFormatterTemplate.cs +++ /dev/null @@ -1,495 +0,0 @@ -// ------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version: 17.0.0.0 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// ------------------------------------------------------------------------------ -namespace MessagePackCompiler.Generator -{ - using System; - using System.Linq; - using System.Collections.Generic; - using MessagePackCompiler.CodeAnalysis; - - /// - /// Class to produce the template output - /// - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] - public partial class StringKeyFormatterTemplate : StringKeyFormatterTemplateBase - { - /// - /// Create the template output - /// - public virtual string TransformText() - { - this.Write(@"// -// THIS (.cs) FILE IS GENERATED BY MPC(MessagePack-CSharp). DO NOT CHANGE IT. -// - -#pragma warning disable 618 -#pragma warning disable 612 -#pragma warning disable 414 -#pragma warning disable 168 -#pragma warning disable CS1591 // document public APIs - -#pragma warning disable SA1129 // Do not use default value type constructor -#pragma warning disable SA1309 // Field names should not begin with underscore -#pragma warning disable SA1312 // Variable names should begin with lower-case letter -#pragma warning disable SA1403 // File may only contain a single namespace -#pragma warning disable SA1649 // File name should match first type name - -namespace "); - this.Write(this.ToStringHelper.ToStringWithCulture(Namespace)); - this.Write("\r\n{\r\n"); - var list = new List>(); -foreach (var objInfo in ObjectSerializationInfos) { - list.Clear(); - foreach (var member in objInfo.Members) { - var binary = EmbedStringHelper.Utf8.GetBytes(member.StringKey); - list.Add(new ValueTuple(member, binary)); - } - - bool isFormatterResolverNecessary = ShouldUseFormatterResolverHelper.ShouldUseFormatterResolver(objInfo.Members); - this.Write(" public sealed class "); - this.Write(this.ToStringHelper.ToStringWithCulture(objInfo.FormatterNameWithoutNameSpace)); - this.Write(" : global::MessagePack.Formatters.IMessagePackFormatter<"); - this.Write(this.ToStringHelper.ToStringWithCulture(objInfo.FullName)); - this.Write(">\r\n"); - foreach (var typeArg in objInfo.GenericTypeParameters.Where(x => x.HasConstraints)) { - this.Write(" where "); - this.Write(this.ToStringHelper.ToStringWithCulture(typeArg.Name)); - this.Write(" : "); - this.Write(this.ToStringHelper.ToStringWithCulture(typeArg.Constraints)); - this.Write("\r\n"); - } - this.Write(" {\r\n"); - foreach (var item in objInfo.Members) { - if (item.CustomFormatterTypeName != null) { - this.Write(" private readonly "); - this.Write(this.ToStringHelper.ToStringWithCulture(item.CustomFormatterTypeName)); - this.Write(" __"); - this.Write(this.ToStringHelper.ToStringWithCulture(item.Name)); - this.Write("CustomFormatter__ = new "); - this.Write(this.ToStringHelper.ToStringWithCulture(item.CustomFormatterTypeName)); - this.Write("();\r\n"); - } - } - for (var i = 0; i < list.Count; i++) { - var member = list[i].Item1; - var binary = list[i].Item2; - this.Write(" // "); - this.Write(this.ToStringHelper.ToStringWithCulture(member.StringKey)); - this.Write("\r\n private static global::System.ReadOnlySpan GetSpan_"); - this.Write(this.ToStringHelper.ToStringWithCulture(member.Name)); - this.Write("() => "); - this.Write(this.ToStringHelper.ToStringWithCulture(EmbedStringHelper.ToByteArrayString(binary))); - this.Write(";\r\n"); - } - if (list.Count != 0) { - this.Write("\r\n"); - } - this.Write(" public void Serialize(ref global::MessagePack.MessagePackWriter writer, "); - this.Write(this.ToStringHelper.ToStringWithCulture(objInfo.FullName)); - this.Write(" value, global::MessagePack.MessagePackSerializerOptions options)\r\n {\r\n"); - if (objInfo.IsClass) { - this.Write(" if (value is null)\r\n {\r\n writer.WriteNil();" + - "\r\n return;\r\n }\r\n\r\n"); - } - - if (isFormatterResolverNecessary) { - this.Write(" var formatterResolver = options.Resolver;\r\n"); - } - - if (objInfo.HasIMessagePackSerializationCallbackReceiver) { - if (objInfo.NeedsCastOnBefore) { - this.Write(" ((global::MessagePack.IMessagePackSerializationCallbackReceiver)value" + - ").OnBeforeSerialize();\r\n"); - } else { - this.Write(" value.OnBeforeSerialize();\r\n"); - } - } - this.Write(" writer.WriteMapHeader("); - this.Write(this.ToStringHelper.ToStringWithCulture(list.Count)); - this.Write(");\r\n"); - foreach (var memberAndBinary in list) { - var member = memberAndBinary.Item1; - this.Write(" writer.WriteRaw(GetSpan_"); - this.Write(this.ToStringHelper.ToStringWithCulture(member.Name)); - this.Write("());\r\n "); - this.Write(this.ToStringHelper.ToStringWithCulture(member.GetSerializeMethodString())); - this.Write(";\r\n"); - } - this.Write(" }\r\n\r\n public "); - this.Write(this.ToStringHelper.ToStringWithCulture(objInfo.FullName)); - this.Write(" Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePac" + - "k.MessagePackSerializerOptions options)\r\n {\r\n if (reader.TryRe" + - "adNil())\r\n {\r\n"); - if (objInfo.IsClass) { - this.Write(" return null;\r\n"); - } else { - this.Write(" throw new global::System.InvalidOperationException(\"typecode is n" + - "ull, struct not supported\");\r\n"); - } - this.Write(" }\r\n\r\n"); - if (objInfo.Members.Length == 0) { - this.Write(" reader.Skip();\r\n var ____result = new "); - this.Write(this.ToStringHelper.ToStringWithCulture(objInfo.GetConstructorString())); - this.Write(";\r\n"); - } else { - this.Write(" options.Security.DepthStep(ref reader);\r\n"); - if (isFormatterResolverNecessary) { - this.Write(" var formatterResolver = options.Resolver;\r\n"); - } - this.Write(" var length = reader.ReadMapHeader();\r\n"); - var canOverwrite = objInfo.ConstructorParameters.Length == 0; - if (canOverwrite) { - this.Write(" var ____result = new "); - this.Write(this.ToStringHelper.ToStringWithCulture(objInfo.GetConstructorString())); - this.Write(";\r\n"); - } else { - foreach (var member in objInfo.Members.Where(x => x.IsWritable || objInfo.ConstructorParameters.Any(p => p.Equals(x)))) { - if (objInfo.ConstructorParameters.All(p => !p.Equals(member))) { - this.Write(" var __"); - this.Write(this.ToStringHelper.ToStringWithCulture(member.Name)); - this.Write("__IsInitialized = false;\r\n"); - } - this.Write(" var __"); - this.Write(this.ToStringHelper.ToStringWithCulture(member.Name)); - this.Write("__ = default("); - this.Write(this.ToStringHelper.ToStringWithCulture(member.Type)); - this.Write(");\r\n"); - } - } - this.Write(@" - for (int i = 0; i < length; i++) - { - var stringKey = global::MessagePack.Internal.CodeGenHelpers.ReadStringSpan(ref reader); - switch (stringKey.Length) - { - default: - FAIL: - reader.Skip(); - continue; -"); - this.Write(this.ToStringHelper.ToStringWithCulture(StringKeyFormatterDeserializeHelper.Classify(objInfo, " ", canOverwrite))); - this.Write("\r\n }\r\n }\r\n\r\n"); - if (!canOverwrite) { - this.Write(" var ____result = new "); - this.Write(this.ToStringHelper.ToStringWithCulture(objInfo.GetConstructorString())); - this.Write(";\r\n"); - foreach (var member in objInfo.Members.Where(x => x.IsWritable && !objInfo.ConstructorParameters.Any(p => p.Equals(x)))) { - this.Write(" if (__"); - this.Write(this.ToStringHelper.ToStringWithCulture(member.Name)); - this.Write("__IsInitialized)\r\n {\r\n ____result."); - this.Write(this.ToStringHelper.ToStringWithCulture(member.Name)); - this.Write(" = __"); - this.Write(this.ToStringHelper.ToStringWithCulture(member.Name)); - this.Write("__;\r\n }\r\n\r\n"); - } - } - } - if (objInfo.HasIMessagePackSerializationCallbackReceiver) { - if (objInfo.NeedsCastOnAfter) { - this.Write(" ((global::MessagePack.IMessagePackSerializationCallbackReceiver)____r" + - "esult).OnAfterDeserialize();\r\n"); - } else { - this.Write(" ____result.OnAfterDeserialize();\r\n"); - } - } - if (objInfo.Members.Length != 0) { - this.Write(" reader.Depth--;\r\n"); - } - this.Write(" return ____result;\r\n }\r\n }\r\n\r\n"); - } - this.Write(@"} - -#pragma warning restore 168 -#pragma warning restore 414 -#pragma warning restore 618 -#pragma warning restore 612 - -#pragma warning restore SA1129 // Do not use default value type constructor -#pragma warning restore SA1309 // Field names should not begin with underscore -#pragma warning restore SA1312 // Variable names should begin with lower-case letter -#pragma warning restore SA1403 // File may only contain a single namespace -#pragma warning restore SA1649 // File name should match first type name -"); - return this.GenerationEnvironment.ToString(); - } - } - #region Base class - /// - /// Base class for this transformation - /// - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] - public class StringKeyFormatterTemplateBase - { - #region Fields - private global::System.Text.StringBuilder generationEnvironmentField; - private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; - private global::System.Collections.Generic.List indentLengthsField; - private string currentIndentField = ""; - private bool endsWithNewline; - private global::System.Collections.Generic.IDictionary sessionField; - #endregion - #region Properties - /// - /// The string builder that generation-time code is using to assemble generated output - /// - protected System.Text.StringBuilder GenerationEnvironment - { - get - { - if ((this.generationEnvironmentField == null)) - { - this.generationEnvironmentField = new global::System.Text.StringBuilder(); - } - return this.generationEnvironmentField; - } - set - { - this.generationEnvironmentField = value; - } - } - /// - /// The error collection for the generation process - /// - public System.CodeDom.Compiler.CompilerErrorCollection Errors - { - get - { - if ((this.errorsField == null)) - { - this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); - } - return this.errorsField; - } - } - /// - /// A list of the lengths of each indent that was added with PushIndent - /// - private System.Collections.Generic.List indentLengths - { - get - { - if ((this.indentLengthsField == null)) - { - this.indentLengthsField = new global::System.Collections.Generic.List(); - } - return this.indentLengthsField; - } - } - /// - /// Gets the current indent we use when adding lines to the output - /// - public string CurrentIndent - { - get - { - return this.currentIndentField; - } - } - /// - /// Current transformation session - /// - public virtual global::System.Collections.Generic.IDictionary Session - { - get - { - return this.sessionField; - } - set - { - this.sessionField = value; - } - } - #endregion - #region Transform-time helpers - /// - /// Write text directly into the generated output - /// - public void Write(string textToAppend) - { - if (string.IsNullOrEmpty(textToAppend)) - { - return; - } - // If we're starting off, or if the previous text ended with a newline, - // we have to append the current indent first. - if (((this.GenerationEnvironment.Length == 0) - || this.endsWithNewline)) - { - this.GenerationEnvironment.Append(this.currentIndentField); - this.endsWithNewline = false; - } - // Check if the current text ends with a newline - if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) - { - this.endsWithNewline = true; - } - // This is an optimization. If the current indent is "", then we don't have to do any - // of the more complex stuff further down. - if ((this.currentIndentField.Length == 0)) - { - this.GenerationEnvironment.Append(textToAppend); - return; - } - // Everywhere there is a newline in the text, add an indent after it - textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); - // If the text ends with a newline, then we should strip off the indent added at the very end - // because the appropriate indent will be added when the next time Write() is called - if (this.endsWithNewline) - { - this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); - } - else - { - this.GenerationEnvironment.Append(textToAppend); - } - } - /// - /// Write text directly into the generated output - /// - public void WriteLine(string textToAppend) - { - this.Write(textToAppend); - this.GenerationEnvironment.AppendLine(); - this.endsWithNewline = true; - } - /// - /// Write formatted text directly into the generated output - /// - public void Write(string format, params object[] args) - { - this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); - } - /// - /// Write formatted text directly into the generated output - /// - public void WriteLine(string format, params object[] args) - { - this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); - } - /// - /// Raise an error - /// - public void Error(string message) - { - System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); - error.ErrorText = message; - this.Errors.Add(error); - } - /// - /// Raise a warning - /// - public void Warning(string message) - { - System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); - error.ErrorText = message; - error.IsWarning = true; - this.Errors.Add(error); - } - /// - /// Increase the indent - /// - public void PushIndent(string indent) - { - if ((indent == null)) - { - throw new global::System.ArgumentNullException("indent"); - } - this.currentIndentField = (this.currentIndentField + indent); - this.indentLengths.Add(indent.Length); - } - /// - /// Remove the last indent that was added with PushIndent - /// - public string PopIndent() - { - string returnValue = ""; - if ((this.indentLengths.Count > 0)) - { - int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; - this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); - if ((indentLength > 0)) - { - returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); - this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); - } - } - return returnValue; - } - /// - /// Remove any indentation - /// - public void ClearIndent() - { - this.indentLengths.Clear(); - this.currentIndentField = ""; - } - #endregion - #region ToString Helpers - /// - /// Utility class to produce culture-oriented representation of an object as a string. - /// - public class ToStringInstanceHelper - { - private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; - /// - /// Gets or sets format provider to be used by ToStringWithCulture method. - /// - public System.IFormatProvider FormatProvider - { - get - { - return this.formatProviderField ; - } - set - { - if ((value != null)) - { - this.formatProviderField = value; - } - } - } - /// - /// This is called from the compile/run appdomain to convert objects within an expression block to a string - /// - public string ToStringWithCulture(object objectToConvert) - { - if ((objectToConvert == null)) - { - throw new global::System.ArgumentNullException("objectToConvert"); - } - System.Type t = objectToConvert.GetType(); - System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { - typeof(System.IFormatProvider)}); - if ((method == null)) - { - return objectToConvert.ToString(); - } - else - { - return ((string)(method.Invoke(objectToConvert, new object[] { - this.formatProviderField }))); - } - } - } - private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); - /// - /// Helper to produce culture-oriented representation of an object as a string - /// - public ToStringInstanceHelper ToStringHelper - { - get - { - return this.toStringHelperField; - } - } - #endregion - } - #endregion -} diff --git a/src/MessagePack.GeneratorCore/Generator/StringKey/StringKeyFormatterTemplate.tt b/src/MessagePack.GeneratorCore/Generator/StringKey/StringKeyFormatterTemplate.tt deleted file mode 100644 index 6a6c3a8e2..000000000 --- a/src/MessagePack.GeneratorCore/Generator/StringKey/StringKeyFormatterTemplate.tt +++ /dev/null @@ -1,164 +0,0 @@ -<#@ template debug="false" hostspecific="false" linePragmas="false" language="C#" #> -<#@ assembly name="System.Core" #> -<#@ import namespace="System" #> -<#@ import namespace="System.Linq" #> -<#@ import namespace="System.Collections.Generic" #> -<#@ import namespace="MessagePackCompiler.CodeAnalysis" #> -// -// THIS (.cs) FILE IS GENERATED BY MPC(MessagePack-CSharp). DO NOT CHANGE IT. -// - -#pragma warning disable 618 -#pragma warning disable 612 -#pragma warning disable 414 -#pragma warning disable 168 -#pragma warning disable CS1591 // document public APIs - -#pragma warning disable SA1129 // Do not use default value type constructor -#pragma warning disable SA1309 // Field names should not begin with underscore -#pragma warning disable SA1312 // Variable names should begin with lower-case letter -#pragma warning disable SA1403 // File may only contain a single namespace -#pragma warning disable SA1649 // File name should match first type name - -namespace <#= Namespace #> -{ -<# var list = new List>(); -foreach (var objInfo in ObjectSerializationInfos) { - list.Clear(); - foreach (var member in objInfo.Members) { - var binary = EmbedStringHelper.Utf8.GetBytes(member.StringKey); - list.Add(new ValueTuple(member, binary)); - } - - bool isFormatterResolverNecessary = ShouldUseFormatterResolverHelper.ShouldUseFormatterResolver(objInfo.Members); #> - public sealed class <#= objInfo.FormatterNameWithoutNameSpace #> : global::MessagePack.Formatters.IMessagePackFormatter<<#= objInfo.FullName #>> -<# foreach (var typeArg in objInfo.GenericTypeParameters.Where(x => x.HasConstraints)) {#> - where <#= typeArg.Name #> : <#= typeArg.Constraints #> -<# }#> - { -<# foreach (var item in objInfo.Members) { #> -<# if (item.CustomFormatterTypeName != null) { #> - private readonly <#= item.CustomFormatterTypeName #> __<#= item.Name #>CustomFormatter__ = new <#= item.CustomFormatterTypeName #>(); -<# } #> -<# } #> -<# for (var i = 0; i < list.Count; i++) { - var member = list[i].Item1; - var binary = list[i].Item2; #> - // <#= member.StringKey #> - private static global::System.ReadOnlySpan GetSpan_<#= member.Name #>() => <#= EmbedStringHelper.ToByteArrayString(binary) #>; -<# } #> -<# if (list.Count != 0) { #> - -<# } #> - public void Serialize(ref global::MessagePack.MessagePackWriter writer, <#= objInfo.FullName #> value, global::MessagePack.MessagePackSerializerOptions options) - { -<# if (objInfo.IsClass) { #> - if (value is null) - { - writer.WriteNil(); - return; - } - -<# } - - if (isFormatterResolverNecessary) { #> - var formatterResolver = options.Resolver; -<# } - - if (objInfo.HasIMessagePackSerializationCallbackReceiver) { - if (objInfo.NeedsCastOnBefore) { #> - ((global::MessagePack.IMessagePackSerializationCallbackReceiver)value).OnBeforeSerialize(); -<# } else { #> - value.OnBeforeSerialize(); -<# } #> -<# } #> - writer.WriteMapHeader(<#= list.Count #>); -<# foreach (var memberAndBinary in list) { - var member = memberAndBinary.Item1; #> - writer.WriteRaw(GetSpan_<#= member.Name #>()); - <#= member.GetSerializeMethodString() #>; -<# } #> - } - - public <#= objInfo.FullName #> Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { -<# if (objInfo.IsClass) { #> - return null; -<# } else { #> - throw new global::System.InvalidOperationException("typecode is null, struct not supported"); -<# } #> - } - -<# if (objInfo.Members.Length == 0) { #> - reader.Skip(); - var ____result = new <#= objInfo.GetConstructorString() #>; -<# } else { #> - options.Security.DepthStep(ref reader); -<# if (isFormatterResolverNecessary) { #> - var formatterResolver = options.Resolver; -<# } #> - var length = reader.ReadMapHeader(); -<# var canOverwrite = objInfo.ConstructorParameters.Length == 0; - if (canOverwrite) { #> - var ____result = new <#= objInfo.GetConstructorString() #>; -<# } else { - foreach (var member in objInfo.Members.Where(x => x.IsWritable || objInfo.ConstructorParameters.Any(p => p.Equals(x)))) { #> -<# if (objInfo.ConstructorParameters.All(p => !p.Equals(member))) { #> - var __<#= member.Name #>__IsInitialized = false; -<# } #> - var __<#= member.Name #>__ = default(<#= member.Type #>); -<# } #> -<# } #> - - for (int i = 0; i < length; i++) - { - var stringKey = global::MessagePack.Internal.CodeGenHelpers.ReadStringSpan(ref reader); - switch (stringKey.Length) - { - default: - FAIL: - reader.Skip(); - continue; -<#= StringKeyFormatterDeserializeHelper.Classify(objInfo, " ", canOverwrite) #> - } - } - -<# if (!canOverwrite) { #> - var ____result = new <#= objInfo.GetConstructorString() #>; -<# foreach (var member in objInfo.Members.Where(x => x.IsWritable && !objInfo.ConstructorParameters.Any(p => p.Equals(x)))) { #> - if (__<#= member.Name #>__IsInitialized) - { - ____result.<#= member.Name #> = __<#= member.Name #>__; - } - -<# } #> -<# } #> -<# } #> -<# if (objInfo.HasIMessagePackSerializationCallbackReceiver) { - if (objInfo.NeedsCastOnAfter) { #> - ((global::MessagePack.IMessagePackSerializationCallbackReceiver)____result).OnAfterDeserialize(); -<# } else { #> - ____result.OnAfterDeserialize(); -<# } #> -<# } #> -<# if (objInfo.Members.Length != 0) { #> - reader.Depth--; -<# } #> - return ____result; - } - } - -<# } #>} - -#pragma warning restore 168 -#pragma warning restore 414 -#pragma warning restore 618 -#pragma warning restore 612 - -#pragma warning restore SA1129 // Do not use default value type constructor -#pragma warning restore SA1309 // Field names should not begin with underscore -#pragma warning restore SA1312 // Variable names should begin with lower-case letter -#pragma warning restore SA1403 // File may only contain a single namespace -#pragma warning restore SA1649 // File name should match first type name diff --git a/src/MessagePack.GeneratorCore/Generator/TemplatePartials.cs b/src/MessagePack.GeneratorCore/Generator/TemplatePartials.cs deleted file mode 100644 index 9e04131b9..000000000 --- a/src/MessagePack.GeneratorCore/Generator/TemplatePartials.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) All contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -#pragma warning disable SA1402 // File may only contain a single type - -using MessagePackCompiler.CodeAnalysis; - -namespace MessagePackCompiler.Generator -{ - public partial class FormatterTemplate : IFormatterTemplate - { - public FormatterTemplate(string @namespace, ObjectSerializationInfo[] objectSerializationInfos) - { - Namespace = @namespace; - ObjectSerializationInfos = objectSerializationInfos; - } - - public string Namespace { get; } - - public ObjectSerializationInfo[] ObjectSerializationInfos { get; } - } - - public partial class StringKeyFormatterTemplate : IFormatterTemplate - { - public StringKeyFormatterTemplate(string @namespace, ObjectSerializationInfo[] objectSerializationInfos) - { - Namespace = @namespace; - ObjectSerializationInfos = objectSerializationInfos; - } - - public string Namespace { get; } - - public ObjectSerializationInfo[] ObjectSerializationInfos { get; } - } - - public partial class ResolverTemplate - { - public ResolverTemplate(string @namespace, string formatterNamespace, string resolverName, IResolverRegisterInfo[] registerInfos) - { - Namespace = @namespace; - FormatterNamespace = formatterNamespace; - ResolverName = resolverName; - RegisterInfos = registerInfos; - } - - public string Namespace { get; } - - public string FormatterNamespace { get; } - - public string ResolverName { get; } - - public IResolverRegisterInfo[] RegisterInfos { get; } - } - - public partial class EnumTemplate - { - public EnumTemplate(string @namespace, EnumSerializationInfo[] enumSerializationInfos) - { - Namespace = @namespace; - EnumSerializationInfos = enumSerializationInfos; - } - - public string Namespace { get; } - - public EnumSerializationInfo[] EnumSerializationInfos { get; } - } - - public partial class UnionTemplate - { - public UnionTemplate(string @namespace, UnionSerializationInfo[] unionSerializationInfos) - { - Namespace = @namespace; - UnionSerializationInfos = unionSerializationInfos; - } - - public string Namespace { get; } - - public UnionSerializationInfo[] UnionSerializationInfos { get; } - } -} diff --git a/src/MessagePack.GeneratorCore/Generator/UnionTemplate.cs b/src/MessagePack.GeneratorCore/Generator/UnionTemplate.cs deleted file mode 100644 index 12e2abb8f..000000000 --- a/src/MessagePack.GeneratorCore/Generator/UnionTemplate.cs +++ /dev/null @@ -1,426 +0,0 @@ -// ------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version: 17.0.0.0 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -// ------------------------------------------------------------------------------ -namespace MessagePackCompiler.Generator -{ - using System.Linq; - using System.Text; - using System.Collections.Generic; - using System; - - /// - /// Class to produce the template output - /// - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] - public partial class UnionTemplate : UnionTemplateBase - { - /// - /// Create the template output - /// - public virtual string TransformText() - { - this.Write(@"// -// THIS (.cs) FILE IS GENERATED BY MPC(MessagePack-CSharp). DO NOT CHANGE IT. -// - -#pragma warning disable 618 -#pragma warning disable 612 -#pragma warning disable 414 -#pragma warning disable 168 -#pragma warning disable CS1591 // document public APIs - -#pragma warning disable SA1403 // File may only contain a single namespace -#pragma warning disable SA1649 // File name should match first type name - -namespace "); - this.Write(this.ToStringHelper.ToStringWithCulture(Namespace)); - this.Write("\r\n{\r\n"); - foreach(var info in UnionSerializationInfos) { - this.Write(" public sealed class "); - this.Write(this.ToStringHelper.ToStringWithCulture(info.Name)); - this.Write("Formatter : global::MessagePack.Formatters.IMessagePackFormatter<"); - this.Write(this.ToStringHelper.ToStringWithCulture(info.FullName)); - this.Write(@"> - { - private readonly global::System.Collections.Generic.Dictionary> typeToKeyAndJumpMap; - private readonly global::System.Collections.Generic.Dictionary keyToJumpMap; - - public "); - this.Write(this.ToStringHelper.ToStringWithCulture(info.Name)); - this.Write("Formatter()\r\n {\r\n this.typeToKeyAndJumpMap = new global::System" + - ".Collections.Generic.Dictionary>("); - this.Write(this.ToStringHelper.ToStringWithCulture(info.SubTypes.Length)); - this.Write(", global::MessagePack.Internal.RuntimeTypeHandleEqualityComparer.Default)\r\n " + - " {\r\n"); - for(var i = 0; i < info.SubTypes.Length; i++) { var item = info.SubTypes[i]; - this.Write(" { typeof("); - this.Write(this.ToStringHelper.ToStringWithCulture(item.Type)); - this.Write(").TypeHandle, new global::System.Collections.Generic.KeyValuePair("); - this.Write(this.ToStringHelper.ToStringWithCulture(item.Key)); - this.Write(", "); - this.Write(this.ToStringHelper.ToStringWithCulture(i)); - this.Write(") },\r\n"); - } - this.Write(" };\r\n this.keyToJumpMap = new global::System.Collections.Ge" + - "neric.Dictionary("); - this.Write(this.ToStringHelper.ToStringWithCulture(info.SubTypes.Length)); - this.Write(")\r\n {\r\n"); - for(var i = 0; i < info.SubTypes.Length; i++) { var item = info.SubTypes[i]; - this.Write(" { "); - this.Write(this.ToStringHelper.ToStringWithCulture(item.Key)); - this.Write(", "); - this.Write(this.ToStringHelper.ToStringWithCulture(i)); - this.Write(" },\r\n"); - } - this.Write(" };\r\n }\r\n\r\n public void Serialize(ref global::MessagePac" + - "k.MessagePackWriter writer, "); - this.Write(this.ToStringHelper.ToStringWithCulture(info.FullName)); - this.Write(@" value, global::MessagePack.MessagePackSerializerOptions options) - { - global::System.Collections.Generic.KeyValuePair keyValuePair; - if (value != null && this.typeToKeyAndJumpMap.TryGetValue(value.GetType().TypeHandle, out keyValuePair)) - { - writer.WriteArrayHeader(2); - writer.WriteInt32(keyValuePair.Key); - switch (keyValuePair.Value) - { -"); - for(var i = 0; i < info.SubTypes.Length; i++) { var item = info.SubTypes[i]; - this.Write(" case "); - this.Write(this.ToStringHelper.ToStringWithCulture(i)); - this.Write(":\r\n global::MessagePack.FormatterResolverExtensions.GetFor" + - "matterWithVerify<"); - this.Write(this.ToStringHelper.ToStringWithCulture(item.Type)); - this.Write(">(options.Resolver).Serialize(ref writer, ("); - this.Write(this.ToStringHelper.ToStringWithCulture(item.Type)); - this.Write(")value, options);\r\n break;\r\n"); - } - this.Write(" default:\r\n break;\r\n }\r\n" + - "\r\n return;\r\n }\r\n\r\n writer.WriteNil();\r\n " + - " }\r\n\r\n public "); - this.Write(this.ToStringHelper.ToStringWithCulture(info.FullName)); - this.Write(@" Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - if (reader.ReadArrayHeader() != 2) - { - throw new global::System.InvalidOperationException(""Invalid Union data was detected. Type:"); - this.Write(this.ToStringHelper.ToStringWithCulture(info.FullName)); - this.Write("\");\r\n }\r\n\r\n options.Security.DepthStep(ref reader);\r\n " + - " var key = reader.ReadInt32();\r\n\r\n if (!this.keyToJumpMap.TryGet" + - "Value(key, out key))\r\n {\r\n key = -1;\r\n }\r\n\r" + - "\n "); - this.Write(this.ToStringHelper.ToStringWithCulture(info.FullName)); - this.Write(" result = null;\r\n switch (key)\r\n {\r\n"); - for(var i = 0; i < info.SubTypes.Length; i++) { var item = info.SubTypes[i]; - this.Write(" case "); - this.Write(this.ToStringHelper.ToStringWithCulture(i)); - this.Write(":\r\n result = ("); - this.Write(this.ToStringHelper.ToStringWithCulture(info.FullName)); - this.Write(")global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify<"); - this.Write(this.ToStringHelper.ToStringWithCulture(item.Type)); - this.Write(">(options.Resolver).Deserialize(ref reader, options);\r\n break;" + - "\r\n"); - } - this.Write(" default:\r\n reader.Skip();\r\n " + - " break;\r\n }\r\n\r\n reader.Depth--;\r\n return result" + - ";\r\n }\r\n }\r\n\r\n"); - } - this.Write(@" -} - -#pragma warning restore 168 -#pragma warning restore 414 -#pragma warning restore 618 -#pragma warning restore 612 - -#pragma warning restore SA1403 // File may only contain a single namespace -#pragma warning restore SA1649 // File name should match first type name -"); - return this.GenerationEnvironment.ToString(); - } - } - #region Base class - /// - /// Base class for this transformation - /// - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] - public class UnionTemplateBase - { - #region Fields - private global::System.Text.StringBuilder generationEnvironmentField; - private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; - private global::System.Collections.Generic.List indentLengthsField; - private string currentIndentField = ""; - private bool endsWithNewline; - private global::System.Collections.Generic.IDictionary sessionField; - #endregion - #region Properties - /// - /// The string builder that generation-time code is using to assemble generated output - /// - protected System.Text.StringBuilder GenerationEnvironment - { - get - { - if ((this.generationEnvironmentField == null)) - { - this.generationEnvironmentField = new global::System.Text.StringBuilder(); - } - return this.generationEnvironmentField; - } - set - { - this.generationEnvironmentField = value; - } - } - /// - /// The error collection for the generation process - /// - public System.CodeDom.Compiler.CompilerErrorCollection Errors - { - get - { - if ((this.errorsField == null)) - { - this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); - } - return this.errorsField; - } - } - /// - /// A list of the lengths of each indent that was added with PushIndent - /// - private System.Collections.Generic.List indentLengths - { - get - { - if ((this.indentLengthsField == null)) - { - this.indentLengthsField = new global::System.Collections.Generic.List(); - } - return this.indentLengthsField; - } - } - /// - /// Gets the current indent we use when adding lines to the output - /// - public string CurrentIndent - { - get - { - return this.currentIndentField; - } - } - /// - /// Current transformation session - /// - public virtual global::System.Collections.Generic.IDictionary Session - { - get - { - return this.sessionField; - } - set - { - this.sessionField = value; - } - } - #endregion - #region Transform-time helpers - /// - /// Write text directly into the generated output - /// - public void Write(string textToAppend) - { - if (string.IsNullOrEmpty(textToAppend)) - { - return; - } - // If we're starting off, or if the previous text ended with a newline, - // we have to append the current indent first. - if (((this.GenerationEnvironment.Length == 0) - || this.endsWithNewline)) - { - this.GenerationEnvironment.Append(this.currentIndentField); - this.endsWithNewline = false; - } - // Check if the current text ends with a newline - if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) - { - this.endsWithNewline = true; - } - // This is an optimization. If the current indent is "", then we don't have to do any - // of the more complex stuff further down. - if ((this.currentIndentField.Length == 0)) - { - this.GenerationEnvironment.Append(textToAppend); - return; - } - // Everywhere there is a newline in the text, add an indent after it - textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); - // If the text ends with a newline, then we should strip off the indent added at the very end - // because the appropriate indent will be added when the next time Write() is called - if (this.endsWithNewline) - { - this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); - } - else - { - this.GenerationEnvironment.Append(textToAppend); - } - } - /// - /// Write text directly into the generated output - /// - public void WriteLine(string textToAppend) - { - this.Write(textToAppend); - this.GenerationEnvironment.AppendLine(); - this.endsWithNewline = true; - } - /// - /// Write formatted text directly into the generated output - /// - public void Write(string format, params object[] args) - { - this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); - } - /// - /// Write formatted text directly into the generated output - /// - public void WriteLine(string format, params object[] args) - { - this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); - } - /// - /// Raise an error - /// - public void Error(string message) - { - System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); - error.ErrorText = message; - this.Errors.Add(error); - } - /// - /// Raise a warning - /// - public void Warning(string message) - { - System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); - error.ErrorText = message; - error.IsWarning = true; - this.Errors.Add(error); - } - /// - /// Increase the indent - /// - public void PushIndent(string indent) - { - if ((indent == null)) - { - throw new global::System.ArgumentNullException("indent"); - } - this.currentIndentField = (this.currentIndentField + indent); - this.indentLengths.Add(indent.Length); - } - /// - /// Remove the last indent that was added with PushIndent - /// - public string PopIndent() - { - string returnValue = ""; - if ((this.indentLengths.Count > 0)) - { - int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; - this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); - if ((indentLength > 0)) - { - returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); - this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); - } - } - return returnValue; - } - /// - /// Remove any indentation - /// - public void ClearIndent() - { - this.indentLengths.Clear(); - this.currentIndentField = ""; - } - #endregion - #region ToString Helpers - /// - /// Utility class to produce culture-oriented representation of an object as a string. - /// - public class ToStringInstanceHelper - { - private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; - /// - /// Gets or sets format provider to be used by ToStringWithCulture method. - /// - public System.IFormatProvider FormatProvider - { - get - { - return this.formatProviderField ; - } - set - { - if ((value != null)) - { - this.formatProviderField = value; - } - } - } - /// - /// This is called from the compile/run appdomain to convert objects within an expression block to a string - /// - public string ToStringWithCulture(object objectToConvert) - { - if ((objectToConvert == null)) - { - throw new global::System.ArgumentNullException("objectToConvert"); - } - System.Type t = objectToConvert.GetType(); - System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { - typeof(System.IFormatProvider)}); - if ((method == null)) - { - return objectToConvert.ToString(); - } - else - { - return ((string)(method.Invoke(objectToConvert, new object[] { - this.formatProviderField }))); - } - } - } - private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); - /// - /// Helper to produce culture-oriented representation of an object as a string - /// - public ToStringInstanceHelper ToStringHelper - { - get - { - return this.toStringHelperField; - } - } - #endregion - } - #endregion -} diff --git a/src/MessagePack.GeneratorCore/Generator/UnionTemplate.tt b/src/MessagePack.GeneratorCore/Generator/UnionTemplate.tt deleted file mode 100644 index a3ecb671d..000000000 --- a/src/MessagePack.GeneratorCore/Generator/UnionTemplate.tt +++ /dev/null @@ -1,115 +0,0 @@ -<#@ template debug="false" hostspecific="false" linePragmas="false" language="C#" #> -<#@ assembly name="System.Core" #> -<#@ import namespace="System.Linq" #> -<#@ import namespace="System.Text" #> -<#@ import namespace="System.Collections.Generic" #> -// -// THIS (.cs) FILE IS GENERATED BY MPC(MessagePack-CSharp). DO NOT CHANGE IT. -// - -#pragma warning disable 618 -#pragma warning disable 612 -#pragma warning disable 414 -#pragma warning disable 168 -#pragma warning disable CS1591 // document public APIs - -#pragma warning disable SA1403 // File may only contain a single namespace -#pragma warning disable SA1649 // File name should match first type name - -namespace <#= Namespace #> -{ -<# foreach(var info in UnionSerializationInfos) { #> - public sealed class <#= info.Name #>Formatter : global::MessagePack.Formatters.IMessagePackFormatter<<#= info.FullName #>> - { - private readonly global::System.Collections.Generic.Dictionary> typeToKeyAndJumpMap; - private readonly global::System.Collections.Generic.Dictionary keyToJumpMap; - - public <#= info.Name #>Formatter() - { - this.typeToKeyAndJumpMap = new global::System.Collections.Generic.Dictionary>(<#= info.SubTypes.Length #>, global::MessagePack.Internal.RuntimeTypeHandleEqualityComparer.Default) - { -<# for(var i = 0; i < info.SubTypes.Length; i++) { var item = info.SubTypes[i]; #> - { typeof(<#= item.Type #>).TypeHandle, new global::System.Collections.Generic.KeyValuePair(<#= item.Key #>, <#= i #>) }, -<# } #> - }; - this.keyToJumpMap = new global::System.Collections.Generic.Dictionary(<#= info.SubTypes.Length #>) - { -<# for(var i = 0; i < info.SubTypes.Length; i++) { var item = info.SubTypes[i]; #> - { <#= item.Key #>, <#= i #> }, -<# } #> - }; - } - - public void Serialize(ref global::MessagePack.MessagePackWriter writer, <#= info.FullName #> value, global::MessagePack.MessagePackSerializerOptions options) - { - global::System.Collections.Generic.KeyValuePair keyValuePair; - if (value != null && this.typeToKeyAndJumpMap.TryGetValue(value.GetType().TypeHandle, out keyValuePair)) - { - writer.WriteArrayHeader(2); - writer.WriteInt32(keyValuePair.Key); - switch (keyValuePair.Value) - { -<# for(var i = 0; i < info.SubTypes.Length; i++) { var item = info.SubTypes[i]; #> - case <#= i #>: - global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify<<#= item.Type #>>(options.Resolver).Serialize(ref writer, (<#= item.Type #>)value, options); - break; -<# } #> - default: - break; - } - - return; - } - - writer.WriteNil(); - } - - public <#= info.FullName #> Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) - { - if (reader.TryReadNil()) - { - return null; - } - - if (reader.ReadArrayHeader() != 2) - { - throw new global::System.InvalidOperationException("Invalid Union data was detected. Type:<#= info.FullName #>"); - } - - options.Security.DepthStep(ref reader); - var key = reader.ReadInt32(); - - if (!this.keyToJumpMap.TryGetValue(key, out key)) - { - key = -1; - } - - <#= info.FullName #> result = null; - switch (key) - { -<# for(var i = 0; i < info.SubTypes.Length; i++) { var item = info.SubTypes[i]; #> - case <#= i #>: - result = (<#= info.FullName #>)global::MessagePack.FormatterResolverExtensions.GetFormatterWithVerify<<#= item.Type #>>(options.Resolver).Deserialize(ref reader, options); - break; -<# } #> - default: - reader.Skip(); - break; - } - - reader.Depth--; - return result; - } - } - -<# } #> - -} - -#pragma warning restore 168 -#pragma warning restore 414 -#pragma warning restore 618 -#pragma warning restore 612 - -#pragma warning restore SA1403 // File may only contain a single namespace -#pragma warning restore SA1649 // File name should match first type name diff --git a/src/MessagePack.GeneratorCore/MessagePack.GeneratorCore.csproj b/src/MessagePack.GeneratorCore/MessagePack.GeneratorCore.csproj deleted file mode 100644 index 2714c70f3..000000000 --- a/src/MessagePack.GeneratorCore/MessagePack.GeneratorCore.csproj +++ /dev/null @@ -1,79 +0,0 @@ - - - - netstandard2.0 - MessagePackCompiler - false - - - - - - - - - - True - True - EnumTemplate.tt - - - True - True - FormatterTemplate.tt - - - True - True - ResolverTemplate.tt - - - True - True - StringKeyFormatterTemplate.tt - - - %(FileName).tt - True - True - - - True - True - UnionTemplate.tt - - - - - - EnumTemplate.cs - TextTemplatingFilePreprocessor - - - FormatterTemplate.cs - TextTemplatingFilePreprocessor - - - ResolverTemplate.cs - TextTemplatingFilePreprocessor - - - StringKeyFormatterTemplate.cs - TextTemplatingFilePreprocessor - MessagePackCompiler.Generator - - - UnionTemplate.cs - TextTemplatingFilePreprocessor - - - - - - - - - - - - diff --git a/src/MessagePack.GeneratorCore/Utils/RoslynExtensions.cs b/src/MessagePack.GeneratorCore/Utils/RoslynExtensions.cs deleted file mode 100644 index b8645e11b..000000000 --- a/src/MessagePack.GeneratorCore/Utils/RoslynExtensions.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) All contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Collections.Generic; -using System.Linq; -using Microsoft.CodeAnalysis; - -namespace MessagePackCompiler -{ - // Utility and Extension methods for Roslyn - internal static class RoslynExtensions - { - public static IEnumerable GetNamedTypeSymbols(this Compilation compilation) - { - return compilation.SyntaxTrees.SelectMany(syntaxTree => - { - var semModel = compilation.GetSemanticModel(syntaxTree); - return syntaxTree.GetRoot() - .DescendantNodes() - .Select(x => semModel.GetDeclaredSymbol(x)) - .OfType(); - }); - } - - public static IEnumerable GetAllMembers(this ITypeSymbol symbol) - { - var t = symbol; - while (t != null) - { - foreach (var item in t.GetMembers()) - { - yield return item; - } - - t = t.BaseType; - } - } - - public static bool ApproximatelyEqual(this INamedTypeSymbol? left, INamedTypeSymbol? right) - { - if (left is IErrorTypeSymbol || right is IErrorTypeSymbol) - { - return left?.ToDisplayString() == right?.ToDisplayString(); - } - else - { - return SymbolEqualityComparer.Default.Equals(left, right); - } - } - } -} diff --git a/src/MessagePack.MSBuild.Tasks/MessagePack.MSBuild.Tasks.csproj b/src/MessagePack.MSBuild.Tasks/MessagePack.MSBuild.Tasks.csproj deleted file mode 100644 index 6f2e11c86..000000000 --- a/src/MessagePack.MSBuild.Tasks/MessagePack.MSBuild.Tasks.csproj +++ /dev/null @@ -1,38 +0,0 @@ - - - - netstandard2.0 - - true - true - false - $(TargetsForTfmSpecificContentInPackage);AddBuildOutputAndDependencies - true - true - Codestin Search App - MSBuild Tasks of MessagePack for C#. - MsgPack;MessagePack;Serialization;Formatter;Serializer;Unity;Xamarin - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/MessagePack.MSBuild.Tasks/MessagePackGenerator.cs b/src/MessagePack.MSBuild.Tasks/MessagePackGenerator.cs deleted file mode 100644 index 69d8f23e5..000000000 --- a/src/MessagePack.MSBuild.Tasks/MessagePackGenerator.cs +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (c) All contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MessagePackCompiler; -using Microsoft.Build.Framework; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Text; - -// synchronous blocks aren't a problem in MSBuild tasks -#pragma warning disable VSTHRD002 // Avoid problematic synchronous waits - -namespace MessagePack.MSBuild.Tasks -{ - public class MessagePackGenerator : Microsoft.Build.Utilities.Task, ICancelableTask - { - private readonly CancellationTokenSource cts = new CancellationTokenSource(); - - [Required] - public ITaskItem[] Compile { get; set; } = null!; - - [Required] - public string GeneratedOutputPath { get; set; } = null!; - - [Required] - public ITaskItem[] ReferencePath { get; set; } = null!; - - public string? DefineConstants { get; set; } - - [Required] - public string ResolverName { get; set; } = null!; - - public string? Namespace { get; set; } - - public bool UseMapMode { get; set; } - - public string[]? ExternalIgnoreTypeNames { get; set; } - - internal CancellationToken CancellationToken => this.cts.Token; - - public void Cancel() => this.cts.Cancel(); - - public override bool Execute() - { - if (string.IsNullOrWhiteSpace(this.ResolverName)) - { - this.Log.LogError($"{nameof(ResolverName)} task parameter must not be set to an empty value."); - return false; - } - - try - { - var compilation = this.CreateCompilation(); - - var generator = new CodeGenerator(x => this.Log.LogMessage(x), CancellationToken.None); - generator.GenerateFileAsync( - compilation, - this.GeneratedOutputPath, - ResolverName, - Namespace, - UseMapMode, - null, - ExternalIgnoreTypeNames).GetAwaiter().GetResult(); - } - catch (Exception ex) - { - this.Log.LogErrorFromException(ex, true); - return false; - } - - return true; - } - - private Compilation CreateCompilation() - { - var parseOptions = new CSharpParseOptions(LanguageVersion.Latest, DocumentationMode.Parse, SourceCodeKind.Regular, this.DefineConstants?.Split(';', ',')); - var syntaxTrees = new List(this.Compile.Length); - foreach (var path in this.Compile) - { - string fullPath = path.GetMetadata("FullPath"); - - if (string.Equals(fullPath, Path.GetFullPath(this.GeneratedOutputPath), StringComparison.OrdinalIgnoreCase)) - { - // Do not include a stale version of the file we are to generate in the compilation. - continue; - } - - using var compile = File.OpenRead(path.ItemSpec); - var sourceText = SourceText.From(compile); - syntaxTrees.Add(CSharpSyntaxTree.ParseText(sourceText, parseOptions, fullPath, cancellationToken: this.CancellationToken)); - } - - var references = - from referencePath in this.ReferencePath - select MetadataReference.CreateFromFile(referencePath.ItemSpec); - - var options = new CSharpCompilationOptions( - OutputKind.DynamicallyLinkedLibrary); - var compilation = CSharpCompilation.Create( - "MsgPackTempProj", - syntaxTrees, - references, - options); - return compilation; - } - } -} diff --git a/src/MessagePack.MSBuild.Tasks/build/MessagePack.MSBuild.Tasks.props b/src/MessagePack.MSBuild.Tasks/build/MessagePack.MSBuild.Tasks.props deleted file mode 100644 index 2139219dd..000000000 --- a/src/MessagePack.MSBuild.Tasks/build/MessagePack.MSBuild.Tasks.props +++ /dev/null @@ -1,12 +0,0 @@ - - - - MessagePack - - - GeneratedResolver - - - false - - diff --git a/src/MessagePack.MSBuild.Tasks/build/MessagePack.MSBuild.Tasks.targets b/src/MessagePack.MSBuild.Tasks/build/MessagePack.MSBuild.Tasks.targets deleted file mode 100644 index e95cf9e90..000000000 --- a/src/MessagePack.MSBuild.Tasks/build/MessagePack.MSBuild.Tasks.targets +++ /dev/null @@ -1,28 +0,0 @@ - - - $(IntermediateOutputPath)mpc_generated.cs - - - - - - - - - - - - - diff --git a/src/MessagePack.SourceGenerator.Unity/CompositeResolverGenerator.cs b/src/MessagePack.SourceGenerator.Unity/CompositeResolverGenerator.cs new file mode 100644 index 000000000..ce1b415a5 --- /dev/null +++ b/src/MessagePack.SourceGenerator.Unity/CompositeResolverGenerator.cs @@ -0,0 +1,76 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Immutable; +using MessagePack.SourceGenerator.Transforms; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using static MessagePack.SourceGenerator.Constants; + +namespace MessagePack.SourceGenerator; + +[Generator] +public class CompositeResolverGenerator : ISourceGenerator +{ + public void Initialize(GeneratorInitializationContext context) + { + context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); + } + + public void Execute(GeneratorExecutionContext context) + { + if (context.SyntaxReceiver is not SyntaxReceiver { ClassDeclarationSyntaxes: { Count: > 0 } } receiver) + { + return; + } + + // Search for a resolver generator attribute, which may be applied to any type in the compilation. + AnalyzerOptions? options = new() { IsGeneratingSource = true }; + foreach (var classDeclByDocument in receiver.ClassDeclarationSyntaxes.GroupBy(td => td.SyntaxTree)) + { + SemanticModel semanticModel = context.Compilation.GetSemanticModel(classDeclByDocument.Key, ignoreAccessibility: true); + foreach (TypeDeclarationSyntax typeDecl in classDeclByDocument) + { + if (semanticModel.GetDeclaredSymbol(typeDecl, context.CancellationToken) is INamedTypeSymbol typeSymbol) + { + if (ParseCompositeResolverAttribute(typeSymbol.GetAttributes()) is { } resolverTypes) + { + CompositeResolverTemplate generator = new() + { + ResolverName = typeSymbol.Name, + ResolverNamespace = typeSymbol.ContainingNamespace.Name, + ResolverInstanceExpressions = AnalyzerUtilities.ResolverSymbolToInstanceExpression(semanticModel, resolverTypes).ToArray(), + }; + context.AddSource(generator.FileName, generator.TransformText()); + } + } + } + } + } + + private static INamedTypeSymbol?[]? ParseCompositeResolverAttribute(ImmutableArray attributes) + { + AttributeData? attribute = attributes.SingleOrDefault(ad => + ad.AttributeClass?.Name == CompositeResolverAttributeName && + ad.AttributeClass?.ContainingNamespace.Name == AttributeNamespace); + if (attribute?.ConstructorArguments.Length > 0 && attribute.ConstructorArguments[0].Kind == TypedConstantKind.Array) + { + return attribute.ConstructorArguments[0].Values.Select(tc => tc.Value as INamedTypeSymbol).ToArray(); + } + + return null; + } + + private class SyntaxReceiver : ISyntaxReceiver + { + internal List ClassDeclarationSyntaxes { get; } = new(); + + public void OnVisitSyntaxNode(SyntaxNode syntaxNode) + { + if (syntaxNode is ClassDeclarationSyntax { AttributeLists.Count: > 0 } classDecl) + { + this.ClassDeclarationSyntaxes.Add(classDecl); + } + } + } +} diff --git a/src/MessagePack.SourceGenerator/.editorconfig b/src/MessagePack.SourceGenerator/.editorconfig new file mode 100644 index 000000000..f82bae653 --- /dev/null +++ b/src/MessagePack.SourceGenerator/.editorconfig @@ -0,0 +1,7 @@ +[*.cs] + +# VSTHRD111: Use ConfigureAwait(bool) +dotnet_diagnostic.VSTHRD111.severity = none + +# CA1062: Validate arguments of public methods +dotnet_diagnostic.CA1062.severity = silent diff --git a/src/MessagePack.SourceGenerator/AnalyzerReleases.Shipped.md b/src/MessagePack.SourceGenerator/AnalyzerReleases.Shipped.md new file mode 100644 index 000000000..99b09b8bb --- /dev/null +++ b/src/MessagePack.SourceGenerator/AnalyzerReleases.Shipped.md @@ -0,0 +1,60 @@ +; Shipped analyzer releases +; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md + +## Release 2.1.80 + +### New Rules + +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +MsgPack001 | Reliability | Disabled | MsgPack001SpecifyOptionsAnalyzer +MsgPack002 | Reliability | Disabled | MsgPack002UseConstantOptionsAnalyzer +MsgPack003 | Usage | Error | MsgPack00xMessagePackAnalyzer +MsgPack004 | Usage | Error | Member needs Key or IgnoreMember attribute +MsgPack005 | Usage | Error | MsgPack00xMessagePackAnalyzer + +## Release 2.3.73-alpha + +### New Rules + +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +MsgPack006 | Usage | Error | MsgPack00xMessagePackAnalyzer + +## Release 2.6.95-alpha + +### New Rules + +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +MsgPack007 | Usage | Error | MsgPack00xMessagePackAnalyzer +MsgPack008 | Usage | Error | MsgPack00xMessagePackAnalyzer + +## Release 3.0.54-alpha + +### New Rules +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +MsgPack009 | Usage | Error | MsgPack00xMessagePackAnalyzer +MsgPack010 | Usage | Warning | Formatter is not accessible to the source generated resolver +MsgPack011 | Usage | Error | MsgPack00xMessagePackAnalyzer +MsgPack012 | Usage | Error | MsgPack00xMessagePackAnalyzer + +## Release 3.0.129-beta + +### New Rules +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +MsgPack013 | Usage | Warning | Formatter has no accessible instance for the source generated resolver + +## Release 3.0.208-rc.1 + +### New Rules + +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +MsgPack014 | Usage | Warning | Formatters of reference types should implement `IMessagePackFormatter` +MsgPack015 | Usage | Warning | MessagePackObjectAttribute.AllowPrivate should be set +MsgPack016 | Usage | Error | KeyAttribute-derived attributes are not supported by AOT formatters +MsgPack017 | Usage | Warning | Property with init accessor and initializer +MsgPack018 | Usage | Error | Unique names required in force map mode \ No newline at end of file diff --git a/src/MessagePack.SourceGenerator/AnalyzerReleases.Unshipped.md b/src/MessagePack.SourceGenerator/AnalyzerReleases.Unshipped.md new file mode 100644 index 000000000..b1b99aaf2 --- /dev/null +++ b/src/MessagePack.SourceGenerator/AnalyzerReleases.Unshipped.md @@ -0,0 +1,3 @@ +; Unshipped analyzer release +; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md + diff --git a/src/MessagePack.SourceGenerator/Analyzers/MsgPack001SpecifyOptionsAnalyzer.cs b/src/MessagePack.SourceGenerator/Analyzers/MsgPack001SpecifyOptionsAnalyzer.cs new file mode 100644 index 000000000..57cc9fbea --- /dev/null +++ b/src/MessagePack.SourceGenerator/Analyzers/MsgPack001SpecifyOptionsAnalyzer.cs @@ -0,0 +1,62 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; + +namespace MessagePack.SourceGenerator.Analyzers; + +/// +/// An analyzer that guards against calling APIs that rely on static, mutable fields defining "default" options. +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public class MsgPack001SpecifyOptionsAnalyzer : DiagnosticAnalyzer +{ + public const string MissingOptionsId = "MsgPack001"; + + public static readonly DiagnosticDescriptor MissingOptionsDescriptor = new DiagnosticDescriptor( + id: MissingOptionsId, + title: new LocalizableResourceString(nameof(Strings.MsgPack001_Title), Strings.ResourceManager, typeof(Strings)), + messageFormat: new LocalizableResourceString(nameof(Strings.MsgPack001_MessageFormat), Strings.ResourceManager, typeof(Strings)), + category: "Reliability", + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: false, + description: new LocalizableResourceString(nameof(Strings.MsgPack001_Description), Strings.ResourceManager, typeof(Strings)), + helpLinkUri: AnalyzerUtilities.GetHelpLink(MissingOptionsId)); + + private static readonly ImmutableArray ReusableSupportedDiagnostics = ImmutableArray.Create(MissingOptionsDescriptor); + + public override ImmutableArray SupportedDiagnostics => ReusableSupportedDiagnostics; + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); + context.EnableConcurrentExecution(); + context.RegisterCompilationStartAction(compilationStartContext => + { + ITypeSymbol? messagePackSerializationOptionsTypeSymbol = compilationStartContext.Compilation.GetTypeByMetadataName("MessagePack.MessagePackSerializerOptions"); + if (messagePackSerializationOptionsTypeSymbol is object) + { + compilationStartContext.RegisterOperationAction(c => this.AnalyzeInvocation(c, messagePackSerializationOptionsTypeSymbol), OperationKind.Invocation); + } + }); + } + + private void AnalyzeInvocation(OperationAnalysisContext ctxt, ITypeSymbol messagePackSerializationOptionsTypeSymbol) + { + var operation = (IInvocationOperation)ctxt.Operation; + + // Is this an invocation on a method defined in the MessagePack assembly? + if (SymbolEqualityComparer.Default.Equals(operation.TargetMethod.ContainingAssembly, messagePackSerializationOptionsTypeSymbol.ContainingAssembly)) + { + var optionsArg = operation.Arguments.FirstOrDefault(arg => SymbolEqualityComparer.Default.Equals(arg.Value?.Type, messagePackSerializationOptionsTypeSymbol)); + if (optionsArg is object && optionsArg.Value.IsImplicit) + { + // The caller is omitting a MessagePackSerializerOptions argument or setting it to null. + ctxt.ReportDiagnostic(Diagnostic.Create(MissingOptionsDescriptor, optionsArg.Value.Syntax.GetLocation())); + } + } + } +} diff --git a/src/MessagePack.SourceGenerator/Analyzers/MsgPack002UseConstantOptionsAnalyzer.cs b/src/MessagePack.SourceGenerator/Analyzers/MsgPack002UseConstantOptionsAnalyzer.cs new file mode 100644 index 000000000..835752d71 --- /dev/null +++ b/src/MessagePack.SourceGenerator/Analyzers/MsgPack002UseConstantOptionsAnalyzer.cs @@ -0,0 +1,86 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; + +namespace MessagePack.SourceGenerator.Analyzers; + +/// +/// An analyzer to guide callers to avoid use of mutable static fields for MessagePackSerializerOptions. +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public class MsgPack002UseConstantOptionsAnalyzer : DiagnosticAnalyzer +{ + public const string MutableSharedOptionsId = "MsgPack002"; + + public static readonly DiagnosticDescriptor MutableSharedOptionsDescriptor = new DiagnosticDescriptor( + id: MutableSharedOptionsId, + title: new LocalizableResourceString(nameof(Strings.MsgPack002_Title), Strings.ResourceManager, typeof(Strings)), + messageFormat: new LocalizableResourceString(nameof(Strings.MsgPack002_MessageFormat), Strings.ResourceManager, typeof(Strings)), + category: "Reliability", + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: false, + description: new LocalizableResourceString(nameof(Strings.MsgPack002_Description), Strings.ResourceManager, typeof(Strings)), + helpLinkUri: AnalyzerUtilities.GetHelpLink(MutableSharedOptionsId)); + + private static readonly ImmutableArray ReusableSupportedDiagnostics = ImmutableArray.Create(MutableSharedOptionsDescriptor); + + public override ImmutableArray SupportedDiagnostics => ReusableSupportedDiagnostics; + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); + context.EnableConcurrentExecution(); + context.RegisterCompilationStartAction(compilationStartContext => + { + ITypeSymbol? messagePackSerializationOptionsTypeSymbol = compilationStartContext.Compilation.GetTypeByMetadataName("MessagePack.MessagePackSerializerOptions"); + if (messagePackSerializationOptionsTypeSymbol is object) + { + compilationStartContext.RegisterOperationAction(c => this.AnalyzeMemberReference(c, messagePackSerializationOptionsTypeSymbol), OperationKind.PropertyReference, OperationKind.FieldReference); + } + }); + } + + private static bool IsLessWritableThanReadable(ISymbol symbol) + { + if (symbol is IPropertySymbol property) + { + if (property.GetMethod is null) + { + // The property has no getter, so the calling code has other problems. + // Don't report a problem. + return true; + } + + if (property.SetMethod is null) + { + // If the property has no setter, we're totally good. + return true; + } + + return property.SetMethod.DeclaredAccessibility < property.GetMethod.DeclaredAccessibility; + } + + if (symbol is IFieldSymbol field) + { + return field.IsReadOnly; + } + + return true; + } + + private void AnalyzeMemberReference(OperationAnalysisContext ctxt, ITypeSymbol messagePackSerializationOptionsTypeSymbol) + { + var memberReferenceOperation = (IMemberReferenceOperation)ctxt.Operation; + var referencedMember = memberReferenceOperation.Member; + if (SymbolEqualityComparer.Default.Equals(memberReferenceOperation.Type, messagePackSerializationOptionsTypeSymbol) && referencedMember.IsStatic && referencedMember.DeclaredAccessibility > Accessibility.Private && !IsLessWritableThanReadable(referencedMember)) + { + // The caller is passing in a value from a mutable static that is as writable as it is readable (a dangerous habit). + // TODO: fix ID, message, etc. to describe the problem. + ctxt.ReportDiagnostic(Diagnostic.Create(MutableSharedOptionsDescriptor, memberReferenceOperation.Syntax.GetLocation())); + } + } +} diff --git a/src/MessagePack.SourceGenerator/Analyzers/MsgPack00xMessagePackAnalyzer.cs b/src/MessagePack.SourceGenerator/Analyzers/MsgPack00xMessagePackAnalyzer.cs new file mode 100644 index 000000000..45fee6e1c --- /dev/null +++ b/src/MessagePack.SourceGenerator/Analyzers/MsgPack00xMessagePackAnalyzer.cs @@ -0,0 +1,468 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using AnalyzerOptions = MessagePack.SourceGenerator.CodeAnalysis.AnalyzerOptions; + +namespace MessagePack.SourceGenerator.Analyzers; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public class MsgPack00xMessagePackAnalyzer : DiagnosticAnalyzer +{ + public const string UseMessagePackObjectAttributeId = "MsgPack003"; + public const string AttributeMessagePackObjectMembersId = "MsgPack004"; + public const string InvalidMessagePackObjectId = "MsgPack005"; + public const string MessagePackFormatterMustBeMessagePackFormatterId = "MsgPack006"; + public const string DeserializingConstructorId = "MsgPack007"; + public const string AOTLimitationsId = "MsgPack008"; + public const string CollidingFormattersId = "MsgPack009"; + public const string InaccessibleFormatterTypeId = "MsgPack010"; + public const string PartialTypeRequiredId = "MsgPack011"; + public const string InaccessibleDataTypeId = "MsgPack012"; + public const string InaccessibleFormatterInstanceId = "MsgPack013"; + public const string NullableReferenceTypeFormatterId = "MsgPack014"; + public const string MessagePackObjectAllowPrivateId = "MsgPack015"; + public const string AOTDerivedKeyId = "MsgPack016"; + public const string AOTInitPropertyId = "MsgPack017"; + public const string CollidingMemberNamesInForceMapModeId = "MsgPack018"; + + internal const string Category = "Usage"; + + public const string MessagePackObjectAttributeShortName = Constants.MessagePackObjectAttributeName; + public const string KeyAttributeShortName = "KeyAttribute"; + public const string IgnoreShortName = "IgnoreMemberAttribute"; + public const string DataMemberShortName = "DataMemberAttribute"; + public const string IgnoreDataMemberShortName = "IgnoreDataMemberAttribute"; + public const string AllowPrivatePropertyName = "AllowPrivate"; + + private const string InvalidMessagePackObjectTitle = "MessagePackObject validation"; + private const DiagnosticSeverity InvalidMessagePackObjectSeverity = DiagnosticSeverity.Error; + + public static readonly DiagnosticDescriptor TypeMustBeMessagePackObject = new DiagnosticDescriptor( + id: UseMessagePackObjectAttributeId, + title: "Use MessagePackObjectAttribute", + category: Category, + messageFormat: "Type must be marked with MessagePackObjectAttribute: {0}", // type.Name + description: "Type must be marked with MessagePackObjectAttribute.", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + helpLinkUri: AnalyzerUtilities.GetHelpLink(UseMessagePackObjectAttributeId)); + + public static readonly DiagnosticDescriptor MessageFormatterMustBeMessagePackFormatter = new DiagnosticDescriptor( + id: MessagePackFormatterMustBeMessagePackFormatterId, + title: "Must be IMessageFormatter", + category: Category, + messageFormat: "Type must be of IMessagePackFormatter: {0}", // type.Name + description: "Type must be of IMessagePackFormatter.", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + helpLinkUri: AnalyzerUtilities.GetHelpLink(MessagePackFormatterMustBeMessagePackFormatterId)); + + public static readonly DiagnosticDescriptor MemberNeedsKey = new DiagnosticDescriptor( + id: AttributeMessagePackObjectMembersId, + title: "Attribute properties and fields of MessagePack objects", + category: Category, + messageFormat: "Properties and fields of MessagePackObject-attributed types require either KeyAttribute or IgnoreMemberAttribute: {0}.{1}", // type.Name + "." + item.Name + description: "Member must be marked with KeyAttribute or IgnoreMemberAttribute.", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + helpLinkUri: AnalyzerUtilities.GetHelpLink(AttributeMessagePackObjectMembersId)); + + public static readonly DiagnosticDescriptor BaseTypeContainsUnattributedPublicMembers = new DiagnosticDescriptor( + id: AttributeMessagePackObjectMembersId, + title: "Attribute properties and fields of MessagePack objects", + category: Category, + messageFormat: "Properties and fields of base types of MessagePackObject-attributed types require either KeyAttribute or IgnoreMemberAttribute: {0}.{1}", // type.Name + "." + item.Name + description: "Member must be marked with KeyAttribute or IgnoreMemberAttribute.", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + helpLinkUri: AnalyzerUtilities.GetHelpLink(AttributeMessagePackObjectMembersId)); + + public static readonly DiagnosticDescriptor InvalidMessagePackObject = new DiagnosticDescriptor( + id: InvalidMessagePackObjectId, + title: InvalidMessagePackObjectTitle, + category: Category, + messageFormat: "Invalid MessagePackObject definition: {0}", // details + description: "Invalid MessagePackObject definition.", + defaultSeverity: InvalidMessagePackObjectSeverity, + isEnabledByDefault: true, + helpLinkUri: AnalyzerUtilities.GetHelpLink(InvalidMessagePackObjectId)); + + public static readonly DiagnosticDescriptor BothStringAndIntKeyAreNull = new DiagnosticDescriptor( + id: InvalidMessagePackObjectId, + title: InvalidMessagePackObjectTitle, + category: Category, + messageFormat: "Both int and string keys are null: {0}.{1}", // type.Name + "." + item.Name + description: "An int or string key must be supplied to the KeyAttribute.", + defaultSeverity: InvalidMessagePackObjectSeverity, + isEnabledByDefault: true, + helpLinkUri: AnalyzerUtilities.GetHelpLink(InvalidMessagePackObjectId)); + + public static readonly DiagnosticDescriptor DoNotMixStringAndIntKeys = new DiagnosticDescriptor( + id: InvalidMessagePackObjectId, + title: InvalidMessagePackObjectTitle, + category: Category, + messageFormat: "All KeyAttribute arguments must be of the same type (either string or int)", + description: "Use string or int keys consistently.", + defaultSeverity: InvalidMessagePackObjectSeverity, + isEnabledByDefault: true, + helpLinkUri: AnalyzerUtilities.GetHelpLink(InvalidMessagePackObjectId)); + + public static readonly DiagnosticDescriptor KeysMustBeUnique = new DiagnosticDescriptor( + id: InvalidMessagePackObjectId, + title: InvalidMessagePackObjectTitle, + category: Category, + messageFormat: "All KeyAttribute arguments must be unique", + description: "Each key must be unique.", + defaultSeverity: InvalidMessagePackObjectSeverity, + isEnabledByDefault: true, + helpLinkUri: AnalyzerUtilities.GetHelpLink(InvalidMessagePackObjectId)); + + public static readonly DiagnosticDescriptor UnionAttributeRequired = new DiagnosticDescriptor( + id: InvalidMessagePackObjectId, + title: InvalidMessagePackObjectTitle, + category: Category, + messageFormat: "This type must carry a UnionAttribute", + description: "A UnionAttribute is required on interfaces and abstract base classes used as serialized types.", + defaultSeverity: InvalidMessagePackObjectSeverity, + isEnabledByDefault: true, + helpLinkUri: AnalyzerUtilities.GetHelpLink(InvalidMessagePackObjectId)); + + // This is important because [Key] on a private member still will not be serialized, which is very confusing until + // one realizes the type is serializing in map mode. + public static readonly DiagnosticDescriptor KeyAnnotatedMemberInMapMode = new DiagnosticDescriptor( + id: InvalidMessagePackObjectId, + title: InvalidMessagePackObjectTitle, + category: Category, + messageFormat: "Types in map mode should not annotate members with KeyAttribute", + description: "When in map mode (by compilation setting or with [MessagePackObject(true)]), internal and public members are automatically included in serialization and should not be annotated with KeyAttribute.", + defaultSeverity: InvalidMessagePackObjectSeverity, + isEnabledByDefault: true, + helpLinkUri: AnalyzerUtilities.GetHelpLink(InvalidMessagePackObjectId)); + + public static readonly DiagnosticDescriptor NoDeserializingConstructor = new DiagnosticDescriptor( + id: DeserializingConstructorId, + title: "Deserializing constructors", + category: Category, + messageFormat: "Cannot find a public constructor", + description: "A deserializable type must carry a public constructor.", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + helpLinkUri: AnalyzerUtilities.GetHelpLink(DeserializingConstructorId)); + + public static readonly DiagnosticDescriptor DeserializingConstructorParameterTypeMismatch = new DiagnosticDescriptor( + id: DeserializingConstructorId, + title: "Deserializing constructors", + category: Category, + messageFormat: "Deserializing constructor parameter type mismatch", + description: "Constructor parameter types must match the serializable members.", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + helpLinkUri: AnalyzerUtilities.GetHelpLink(DeserializingConstructorId)); + + public static readonly DiagnosticDescriptor DeserializingConstructorParameterIndexMissing = new DiagnosticDescriptor( + id: DeserializingConstructorId, + title: "Deserializing constructors", + category: Category, + messageFormat: "Deserializing constructor parameter count mismatch", + description: "The deserializing constructor parameter count must meet or exceed the number of serialized members.", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + helpLinkUri: AnalyzerUtilities.GetHelpLink(DeserializingConstructorId)); + + public static readonly DiagnosticDescriptor DeserializingConstructorParameterNameMissing = new DiagnosticDescriptor( + id: DeserializingConstructorId, + title: "Deserializing constructors", + category: Category, + messageFormat: "Deserializing constructor parameter name mismatch", + description: "Parameter names must match the serialized members' named keys.", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + helpLinkUri: AnalyzerUtilities.GetHelpLink(DeserializingConstructorId)); + + public static readonly DiagnosticDescriptor DeserializingConstructorParameterNameDuplicate = new DiagnosticDescriptor( + id: DeserializingConstructorId, + title: "Deserializing constructors", + category: Category, + messageFormat: "Duplicate matched constructor parameter name", + description: "Parameter names must match the serialized members' named keys.", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + helpLinkUri: AnalyzerUtilities.GetHelpLink(DeserializingConstructorId)); + + public static readonly DiagnosticDescriptor AotUnionAttributeRequiresTypeArg = new DiagnosticDescriptor( + id: AOTLimitationsId, + title: "AOT limitations", + category: Category, + messageFormat: "The source generator only supports UnionAttribute with a Type argument", + description: "Use a type argument with UnionAttribute.", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + helpLinkUri: AnalyzerUtilities.GetHelpLink(AOTLimitationsId)); + + public static readonly DiagnosticDescriptor AotArrayRankTooHigh = new DiagnosticDescriptor( + id: AOTLimitationsId, + title: "AOT limitations", + category: Category, + messageFormat: "Array rank too high for built-in array formatters", + description: "Avoid excessively high array ranks, or write a custom formatter.", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + helpLinkUri: AnalyzerUtilities.GetHelpLink(AOTLimitationsId)); + + public static readonly DiagnosticDescriptor AOTDerivedKeyAttribute = new DiagnosticDescriptor( + id: AOTDerivedKeyId, + title: "KeyAttribute derivatives", + category: Category, + messageFormat: "KeyAttribute-derived attributes are not supported by AOT formatters", + description: "Use [Key(x)] attributes directly, or switch off source generation for this type using [MessagePackObject(SuppressSourceGeneration = true)].", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + helpLinkUri: AnalyzerUtilities.GetHelpLink(AOTDerivedKeyId)); + + public static readonly DiagnosticDescriptor AOTInitProperty = new( + id: AOTInitPropertyId, + title: "Property with init accessor and initializer", + category: Category, + messageFormat: "The value of a property with an init accessor and an initializer will be reset to the default value for the type upon deserialization when no value for them is deserialized", + description: "Due to a limitation in the C# language, properties with init accessor must be set by source generated deserializers unconditionally. When the data stream lacks a value for the property, it will be set with the default value for the property type, overriding a default that an initializer might set.", + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true, + helpLinkUri: AnalyzerUtilities.GetHelpLink(AOTInitPropertyId)); + + public static readonly DiagnosticDescriptor CollidingFormatters = new( + id: CollidingFormattersId, + title: "Colliding formatters", + category: Category, + messageFormat: "Multiple formatters for type {0} found", + description: "Only one formatter per type is allowed.", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + helpLinkUri: AnalyzerUtilities.GetHelpLink(CollidingFormattersId)); + + public static readonly DiagnosticDescriptor InaccessibleFormatterInstance = new( + id: InaccessibleFormatterInstanceId, + title: "Inaccessible formatter", + category: Category, + messageFormat: "Formatter should declare a default constructor with at least internal visibility or a public static readonly field named Instance that returns the singleton", + description: "The auto-generated resolver cannot construct this formatter without a constructor. It will be omitted from the resolver.", + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true, + helpLinkUri: AnalyzerUtilities.GetHelpLink(InaccessibleFormatterInstanceId)); + + public static readonly DiagnosticDescriptor InaccessibleFormatterType = new( + id: InaccessibleFormatterTypeId, + title: "Inaccessible formatter", + category: Category, + messageFormat: "Formatter should be declared with at least internal visibility", + description: "The auto-generated resolver cannot access this formatter. It will be omitted from the resolver.", + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true, + helpLinkUri: AnalyzerUtilities.GetHelpLink(InaccessibleFormatterTypeId)); + + public static readonly DiagnosticDescriptor PartialTypeRequired = new( + id: PartialTypeRequiredId, + title: "Partial type required", + category: Category, + messageFormat: "Types with private, serializable members must be declared as partial, including nesting types", + description: "When a data type has serializable members that may only be accessible to the class itself (e.g. private or protected members), the type must be declared as partial to allow source generation of the formatter as a nested type. When a data type is itself a nested type, its declaring types must also be partial.", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + helpLinkUri: AnalyzerUtilities.GetHelpLink(PartialTypeRequiredId)); + + public static readonly DiagnosticDescriptor InaccessibleDataType = new( + id: InaccessibleDataTypeId, + title: "Internally accessible data type required", + category: Category, + messageFormat: "This MessagePack formattable type must have at least internal visibility", + description: "MessagePack serializable objects must be at least internally accessible so a source-generated formatter can access it.", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + helpLinkUri: AnalyzerUtilities.GetHelpLink(InaccessibleDataTypeId)); + + public static readonly DiagnosticDescriptor NullableReferenceTypeFormatter = new( + id: NullableReferenceTypeFormatterId, + title: "Format nullable reference types", + category: Category, + messageFormat: "Implement IMessagePackFormatter<{0}?> (with nullable annotation)", + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true, + helpLinkUri: AnalyzerUtilities.GetHelpLink(NullableReferenceTypeFormatterId)); + + public static readonly DiagnosticDescriptor MessagePackObjectAllowPrivateRequired = new( + id: MessagePackObjectAllowPrivateId, + title: "MessagePackObjectAttribute.AllowPrivate should be set", + category: Category, + messageFormat: "MessagePackObjectAttribute.AllowPrivate should be set to true because the type is not public and/or at least one of its non-public members are attributed for serialization", + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true, + helpLinkUri: AnalyzerUtilities.GetHelpLink(MessagePackObjectAllowPrivateId)); + + public static readonly DiagnosticDescriptor CollidingMemberNamesInForceMapMode = new( + id: CollidingMemberNamesInForceMapModeId, + title: "Unique names required in force map mode", + category: Category, + messageFormat: "All serialized member names must be unique in force map mode, but this member redeclares a member with the same name as one found on a base type", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + helpLinkUri: AnalyzerUtilities.GetHelpLink(CollidingMemberNamesInForceMapModeId)); + + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create( + TypeMustBeMessagePackObject, + MessageFormatterMustBeMessagePackFormatter, + MemberNeedsKey, + BaseTypeContainsUnattributedPublicMembers, + InvalidMessagePackObject, + BothStringAndIntKeyAreNull, + DoNotMixStringAndIntKeys, + KeysMustBeUnique, + UnionAttributeRequired, + KeyAnnotatedMemberInMapMode, + NoDeserializingConstructor, + DeserializingConstructorParameterTypeMismatch, + DeserializingConstructorParameterIndexMissing, + DeserializingConstructorParameterNameMissing, + DeserializingConstructorParameterNameDuplicate, + AotUnionAttributeRequiresTypeArg, + AotArrayRankTooHigh, + AOTDerivedKeyAttribute, + AOTInitProperty, + CollidingFormatters, + InaccessibleFormatterInstance, + InaccessibleFormatterType, + PartialTypeRequired, + InaccessibleDataType, + NullableReferenceTypeFormatter, + MessagePackObjectAllowPrivateRequired, + CollidingMemberNamesInForceMapMode); + + public override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); + context.RegisterCompilationStartAction(context => + { + if (ReferenceSymbols.TryCreate(context.Compilation, out ReferenceSymbols? typeReferences)) + { + // Search the compilation for implementations of IMessagePackFormatter. + ImmutableHashSet formatterTypes = this.SearchForFormatters(context.Compilation.Assembly.GlobalNamespace).ToImmutableHashSet(); + + AnalyzerOptions options = new AnalyzerOptions() + .WithFormatterTypes(ImmutableArray.Empty, formatterTypes) + .WithAssemblyAttributes(context.Compilation.Assembly.GetAttributes(), context.CancellationToken); + context.RegisterSymbolAction(context => this.AnalyzeSymbol(context, typeReferences, options), SymbolKind.NamedType); + context.RegisterSymbolStartAction(context => SymbolStartAction(context, typeReferences, options), SymbolKind.NamedType); + } + }); + } + + private static void SymbolStartAction(SymbolStartAnalysisContext context, ReferenceSymbols typeReferences, AnalyzerOptions options) + { + INamedTypeSymbol declaredSymbol = (INamedTypeSymbol)context.Symbol; + QualifiedNamedTypeName typeName = new(declaredSymbol, ImmutableStack.Empty); + + // If this is a formatter, confirm that it meets requirements. + if (options.KnownFormattersByName.TryGetValue(typeName, out FormatterDescriptor? formatter)) + { + // Call out any formattable reference types that are not nullable. + INamedTypeSymbol[] missing = declaredSymbol.Interfaces + .Where(iface => iface.IsGenericType) + .Where(iface => SymbolEqualityComparer.Default.Equals(iface.ConstructUnboundGenericType(), typeReferences.MessagePackFormatterOfT) + && iface.TypeArguments is [INamedTypeSymbol { IsReferenceType: true, NullableAnnotation: NullableAnnotation.NotAnnotated } a]) + .Select(iface => (INamedTypeSymbol)iface.TypeArguments[0]) + .ToArray(); + + if (missing.Length > 0) + { + // Look for the base list so we can report the diagnostic(s) at the actual type argument. + context.RegisterSyntaxNodeAction( + context => + { + BaseListSyntax baseList = (BaseListSyntax)context.Node; + + // Find the location of the actual generic type argument if we can. + foreach (BaseTypeSyntax baseTypeSyntax in baseList.Types) + { + if (baseTypeSyntax.Type is GenericNameSyntax { TypeArgumentList: { Arguments: [TypeSyntax typeArg] } }) + { + ITypeSymbol? actual = context.SemanticModel.GetTypeInfo(typeArg, context.CancellationToken).Type; + if (missing.Any(m => SymbolEqualityComparer.Default.Equals(actual, m))) + { + Location location = typeArg.GetLocation(); + context.ReportDiagnostic(Diagnostic.Create(NullableReferenceTypeFormatter, location, typeArg.ToString())); + break; + } + } + } + }, + SyntaxKind.BaseList); + } + } + } + + private void AnalyzeSymbol(SymbolAnalysisContext context, ReferenceSymbols typeReferences, AnalyzerOptions options) + { + INamedTypeSymbol declaredSymbol = (INamedTypeSymbol)context.Symbol; + QualifiedNamedTypeName typeName = new(declaredSymbol, ImmutableStack.Empty); + + // If this is a formatter, confirm that it meets requirements. + if (options.KnownFormattersByName.TryGetValue(typeName, out FormatterDescriptor? formatter)) + { + // Look for colliding formatters (multiple formatters that want to format the same type). + foreach (FormattableType formattableType in options.GetCollidingFormatterDataTypes(typeName)) + { + context.ReportDiagnostic(Diagnostic.Create(CollidingFormatters, declaredSymbol.Locations[0], formattableType.Name.GetQualifiedName(Qualifiers.Namespace))); + } + + if (!formatter.ExcludeFromSourceGeneratedResolver && formatter.InaccessibleDescriptor is { } inaccessible) + { + context.ReportDiagnostic(Diagnostic.Create(inaccessible, declaredSymbol.Locations[0])); + } + } + + switch (declaredSymbol.TypeKind) + { + case TypeKind.Interface when declaredSymbol.GetAttributes().Any(x2 => SymbolEqualityComparer.Default.Equals(x2.AttributeClass, typeReferences.UnionAttribute)): + case TypeKind.Class or TypeKind.Struct when declaredSymbol.GetAttributes().Any(x2 => SymbolEqualityComparer.Default.Equals(x2.AttributeClass, typeReferences.MessagePackObjectAttribute)): + TypeCollector.Collect(context.Compilation, options, typeReferences, context.ReportDiagnostic, declaredSymbol, context.CancellationToken); + break; + } + } + + private IEnumerable SearchForFormatters(INamespaceOrTypeSymbol container) + { + if (container is INamespaceSymbol ns) + { + foreach (INamespaceSymbol childNamespace in ns.GetNamespaceMembers()) + { + foreach (FormatterDescriptor x in this.SearchForFormatters(childNamespace)) + { + yield return x; + } + } + } + + foreach (INamedTypeSymbol type in container.GetTypeMembers()) + { + if (type.DeclaringSyntaxReferences.FirstOrDefault()?.SyntaxTree.FilePath.Contains("MessagePack.SourceGenerator") is true) + { + // We do not want to find source generated formatters during analysis. + continue; + } + + if (FormatterDescriptor.TryCreate(type, out FormatterDescriptor? formatter)) + { + yield return formatter; + } + + foreach (FormatterDescriptor nested in this.SearchForFormatters(type)) + { + yield return nested; + } + } + } +} diff --git a/src/MessagePack.SourceGenerator/CodeAnalysis/AnalyzerOptions.cs b/src/MessagePack.SourceGenerator/CodeAnalysis/AnalyzerOptions.cs new file mode 100644 index 000000000..f117ee557 --- /dev/null +++ b/src/MessagePack.SourceGenerator/CodeAnalysis/AnalyzerOptions.cs @@ -0,0 +1,289 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma warning disable SA1402 // File may only contain a single type + +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; + +namespace MessagePack.SourceGenerator.CodeAnalysis; + +/// +/// Options for the analyzer and source generator. +/// +/// +/// These options are typically gathered from attributes in the compilation. +/// +public record AnalyzerOptions +{ + private readonly ImmutableHashSet knownFormatters = ImmutableHashSet.Empty; + + private readonly ImmutableDictionary> collidingFormatters = ImmutableDictionary>.Empty; + + /// + /// Gets the set fully qualified names of types that are assumed to have custom formatters written that will be included by a resolver by the program. + /// + public ImmutableHashSet AssumedFormattableTypes { get; init; } = ImmutableHashSet.Empty; + + /// + /// Gets the set of custom formatters that should be considered by the analyzer and included in the generated resolver. + /// + public ImmutableHashSet KnownFormatters + { + get => this.knownFormatters; + init + { + this.knownFormatters = value; + this.KnownFormattersByName = value.ToImmutableDictionary(f => f.Name); + + Dictionary> formattableTypes = new(); + bool collisionsEncountered = false; + foreach (FormatterDescriptor formatter in value) + { + if (formatter.ExcludeFromSourceGeneratedResolver) + { + continue; + } + + foreach (FormattableType dataType in formatter.FormattableTypes) + { + if (formattableTypes.ContainsKey(dataType)) + { + formattableTypes[dataType] = formattableTypes[dataType].Add(formatter); + collisionsEncountered = true; + } + else + { + formattableTypes.Add(dataType, ImmutableArray.Create(formatter)); + } + } + } + + var collidingFormatters = ImmutableDictionary>.Empty; + if (collisionsEncountered) + { + foreach (KeyValuePair> kvp in formattableTypes) + { + if (kvp.Value.Length > 1) + { + foreach (FormatterDescriptor collidingFormatter in kvp.Value) + { + if (collidingFormatters.TryGetValue(collidingFormatter.Name, out ImmutableArray collidingTypes)) + { + collidingFormatters = collidingFormatters.SetItem(collidingFormatter.Name, collidingTypes.Add(kvp.Key)); + } + else + { + collidingFormatters = collidingFormatters.Add(collidingFormatter.Name, ImmutableArray.Create(kvp.Key)); + } + } + } + } + } + + this.collidingFormatters = collidingFormatters; + } + } + + public ImmutableDictionary KnownFormattersByName { get; private init; } = ImmutableDictionary.Empty; + + public GeneratorOptions Generator { get; init; } = new(); + + /// + /// Gets a value indicating whether the analyzer is generating source code. + /// + public bool IsGeneratingSource { get; init; } + + internal AnalyzerOptions WithFormatterTypes(ImmutableArray formattableTypes, ImmutableHashSet customFormatters) + { + return this with + { + AssumedFormattableTypes = ImmutableHashSet.CreateRange(formattableTypes).Union(customFormatters.SelectMany(t => t.FormattableTypes)), + KnownFormatters = customFormatters, + }; + } + + /// + /// Modifies these options based on the attributes on the assembly being compiled. + /// + /// The assembly-level attributes. + /// A cancellation token. + /// The modified set of options. + internal AnalyzerOptions WithAssemblyAttributes(ImmutableArray assemblyAttributes, CancellationToken cancellationToken) + { + ImmutableHashSet customFormatters = AnalyzerUtilities.ParseKnownFormatterAttribute(assemblyAttributes, cancellationToken).Union(this.KnownFormatters); + ImmutableArray customFormattedTypes = this.AssumedFormattableTypes.Union(AnalyzerUtilities.ParseAssumedFormattableAttribute(assemblyAttributes, cancellationToken)).ToImmutableArray(); + return this.WithFormatterTypes(customFormattedTypes, customFormatters); + } + + internal ImmutableArray GetCollidingFormatterDataTypes(QualifiedTypeName formatter) => this.collidingFormatters.GetValueOrDefault(formatter, ImmutableArray.Empty); + + public virtual bool Equals(AnalyzerOptions? other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (this.GetType() != other.GetType()) + { + return false; + } + + return this.knownFormatters.SetEquals(other.knownFormatters) + && this.AssumedFormattableTypes.SetEquals(other.AssumedFormattableTypes) + && this.Generator.Equals(other.Generator) + && this.IsGeneratingSource == other.IsGeneratingSource; + } + + public override int GetHashCode() + { + int hashCode = 0; + + hashCode = Hash(hashCode, this.knownFormatters.Count); + hashCode = Hash(hashCode, this.AssumedFormattableTypes.Count); + hashCode = Hash(hashCode, this.Generator.GetHashCode()); + + return hashCode; + + static int Hash(int hashCode, int value) + { + return (hashCode * 31) + value; + } + } +} + +/// +/// Customizes aspects of source generated formatters. +/// +public record FormattersOptions +{ + /// + /// Gets a value indicating whether types will be serialized with their property names as well as their values in a key=value dictionary, as opposed to an array of values. + /// + public bool UsesMapMode { get; init; } +} + +/// +/// Describes the generated resolver. +/// +public record ResolverOptions +{ + /// + /// Gets the name to use for the resolver. + /// + public QualifiedNamedTypeName Name { get; init; } = new(TypeKind.Class) + { + Container = new NamespaceTypeContainer("MessagePack"), + Name = "GeneratedMessagePackResolver", + }; +} + +/// +/// Customizes AOT source generation of formatters for custom types. +/// +public record GeneratorOptions +{ + /// + /// Gets options for the generated resolver. + /// + public ResolverOptions Resolver { get; init; } = new(); + + /// + /// Gets options for the generated formatter. + /// + public FormattersOptions Formatters { get; init; } = new(); +} + +/// +/// Describes a custom formatter. +/// +/// The name of the type that implements at least one IMessagePackFormatter interface. If the formatter is a generic type, this should not include any generic type parameters. +/// Either ".ctor" or the name of a static field or property that will return an instance of the formatter. +/// The type name to use when referring to an instance of the formatter. Usually the same as but may be different if returns a different type. +/// The type arguments that appear in each implemented IMessagePackFormatter interface. When generic, these should be the full name of their type definitions. +public record FormatterDescriptor(QualifiedNamedTypeName Name, string? InstanceProvidingMember, QualifiedTypeName InstanceTypeName, ImmutableHashSet FormattableTypes) +{ + /// + /// Creates a descriptor for a formatter, if the given type implements at least one IMessagePackFormatter interface. + /// + /// The type symbol for the formatter. + /// Receives the formatter descriptor, if applicable. + /// if represents a formatter. + public static bool TryCreate(INamedTypeSymbol type, [NotNullWhen(true)] out FormatterDescriptor? formatter) + { + if (type.IsAbstract) + { + formatter = null; + return false; + } + + var formattedTypes = + AnalyzerUtilities.SearchTypeForFormatterImplementations(type) + .Select(i => new FormattableType(i, type.ContainingAssembly)) + .ToImmutableHashSet(); + if (formattedTypes.IsEmpty) + { + formatter = null; + return false; + } + + IFieldSymbol? instanceField = type.GetMembers("Instance").OfType() + .FirstOrDefault(m => m.IsStatic && m.DeclaredAccessibility == Accessibility.Public && m.IsReadOnly); + IMethodSymbol? ctor = type.InstanceConstructors.FirstOrDefault(ctor => ctor.Parameters.Length == 0 && ctor.DeclaredAccessibility >= Accessibility.Internal); + string? instanceProvidingMember = instanceField?.Name ?? ctor?.Name ?? null; + QualifiedTypeName instanceTypeName = QualifiedTypeName.Create(instanceField?.Type ?? type); + + formatter = new FormatterDescriptor(new QualifiedNamedTypeName(type), instanceProvidingMember, instanceTypeName, formattedTypes) + { + InaccessibleDescriptor = + CodeAnalysisUtilities.FindInaccessibleTypes(type).Any() ? MsgPack00xMessagePackAnalyzer.InaccessibleFormatterType : + instanceProvidingMember is null ? MsgPack00xMessagePackAnalyzer.InaccessibleFormatterInstance : + null, + ExcludeFromSourceGeneratedResolver = + type.GetAttributes().Any(a => a.AttributeClass?.Name == Constants.ExcludeFormatterFromSourceGeneratedResolverAttributeName && a.AttributeClass?.ContainingNamespace.Name == Constants.AttributeNamespace), + }; + + return true; + } + + public DiagnosticDescriptor? InaccessibleDescriptor { get; init; } + + public bool ExcludeFromSourceGeneratedResolver { get; init; } + + public string InstanceExpression => this.InstanceProvidingMember == ".ctor" + ? $"new {this.Name.GetQualifiedName(genericStyle: GenericParameterStyle.Arguments)}()" + : $"{this.Name.GetQualifiedName(genericStyle: GenericParameterStyle.Arguments)}.{this.InstanceProvidingMember}"; + + public virtual bool Equals(FormatterDescriptor? other) + { + return other is not null + && this.Name.Equals(other.Name) + && this.InstanceProvidingMember == other.InstanceProvidingMember + && this.InstanceTypeName.Equals(other.InstanceTypeName) + && this.FormattableTypes.SetEquals(other.FormattableTypes) + && this.InaccessibleDescriptor == other.InaccessibleDescriptor + && this.ExcludeFromSourceGeneratedResolver == other.ExcludeFromSourceGeneratedResolver; + } + + public override int GetHashCode() => this.Name.GetHashCode(); +} + +/// +/// Describes a formattable type. +/// +/// The name of the formattable type. +/// if the formatter and the formatted types are declared in the same assembly. +public record FormattableType(QualifiedTypeName Name, bool IsFormatterInSameAssembly) +{ + public FormattableType(ITypeSymbol type, IAssemblySymbol? formatterAssembly) + : this(QualifiedTypeName.Create(type), SymbolEqualityComparer.Default.Equals(type.ContainingAssembly, formatterAssembly)) + { + } +} diff --git a/src/MessagePack.SourceGenerator/CodeAnalysis/CodeAnalysisUtilities.cs b/src/MessagePack.SourceGenerator/CodeAnalysis/CodeAnalysisUtilities.cs new file mode 100644 index 000000000..bfa8c5b8a --- /dev/null +++ b/src/MessagePack.SourceGenerator/CodeAnalysis/CodeAnalysisUtilities.cs @@ -0,0 +1,100 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace MessagePack.SourceGenerator.CodeAnalysis; + +public static class CodeAnalysisUtilities +{ + private static readonly HashSet InvalidFileNameChars = new(Path.GetInvalidFileNameChars()); + + static CodeAnalysisUtilities() + { + // Roslyn really doesn't like angle brackets in file names, even on operating systems that allow them (e.g. linux). + // See https://github.com/dotnet/roslyn/issues/67653 + InvalidFileNameChars.Add('<'); + InvalidFileNameChars.Add('>'); + } + + public static string QualifyWithOptionalNamespace(string leafTypeOrNamespace, string? baseNamespace) + { + return string.IsNullOrEmpty(baseNamespace) ? leafTypeOrNamespace : (baseNamespace!.EndsWith("::") ? $"{baseNamespace}{leafTypeOrNamespace}" : $"{baseNamespace}.{leafTypeOrNamespace}"); + } + + public static string AppendNameToNamespace(string left, string? right) + { + return string.IsNullOrEmpty(right) ? left : $"{left}.{right}"; + } + + public static string GetSanitizedFileName(string fileName) + { + foreach (char c in InvalidFileNameChars) + { + fileName = fileName.Replace(c, '_'); + } + + return fileName; + } + + internal static IEnumerable FindInaccessibleTypes(ITypeSymbol target) + { + return from x in EnumerateTypeAndContainingTypes(target) + where x.Symbol.DeclaredAccessibility is not (Accessibility.Public or Accessibility.Internal or Accessibility.ProtectedOrFriend or Accessibility.ProtectedOrInternal) + select x.FirstDeclaration; + } + + internal static Accessibility GetEffectiveAccessibility(this ITypeSymbol target) + { + Accessibility effective = target.DeclaredAccessibility; + while (target.ContainingType is not null) + { + target = target.ContainingType; + if (target.DeclaredAccessibility < effective) + { + effective = target.DeclaredAccessibility; + } + } + + return effective; + } + + internal static IEnumerable<(ITypeSymbol Symbol, BaseTypeDeclarationSyntax? FirstDeclaration)> EnumerateTypeAndContainingTypes(ITypeSymbol target) + { + ITypeSymbol? focusedSymbol = target; + while (focusedSymbol is not null) + { + yield return (focusedSymbol, focusedSymbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() as BaseTypeDeclarationSyntax); + focusedSymbol = focusedSymbol.ContainingType; + } + } + + internal static int GetArity(ITypeSymbol dataType) + => dataType switch + { + INamedTypeSymbol namedType => namedType.Arity, + IArrayTypeSymbol arrayType => GetArity(arrayType.ElementType), + ITypeParameterSymbol => 0, + _ => throw new NotSupportedException(), + }; + + internal static ImmutableArray GetTypeParameters(ITypeSymbol dataType, ImmutableStack? recursionGuard = null) + => dataType switch + { + INamedTypeSymbol namedType => namedType.TypeParameters.Select(t => GenericTypeParameterInfo.Create(t, recursionGuard)).ToImmutableArray(), + IArrayTypeSymbol arrayType => GetTypeParameters(arrayType.ElementType, recursionGuard), + ITypeParameterSymbol => ImmutableArray.Empty, + _ => throw new NotSupportedException(), + }; + + internal static ImmutableArray GetTypeArguments(ITypeSymbol dataType, ImmutableStack? recursionGuard = null) + => dataType switch + { + INamedTypeSymbol namedType => namedType.TypeArguments.Select(t => QualifiedTypeName.Create(t, recursionGuard)).ToImmutableArray(), + IArrayTypeSymbol arrayType => GetTypeArguments(arrayType.ElementType, recursionGuard), + ITypeParameterSymbol => ImmutableArray.Empty, + _ => throw new NotSupportedException(), + }; +} diff --git a/src/MessagePack.SourceGenerator/CodeAnalysis/CustomFormatterRegisterInfo.cs b/src/MessagePack.SourceGenerator/CodeAnalysis/CustomFormatterRegisterInfo.cs new file mode 100644 index 000000000..b6a6b5382 --- /dev/null +++ b/src/MessagePack.SourceGenerator/CodeAnalysis/CustomFormatterRegisterInfo.cs @@ -0,0 +1,18 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace MessagePack.SourceGenerator.CodeAnalysis; + +public record CustomFormatterRegisterInfo : ResolverRegisterInfo +{ + public required FormatterDescriptor CustomFormatter { get; init; } + + public required FormattableType FormattableDataType { get; init; } + + public override string GetFormatterInstanceForResolver() + { + return this.CustomFormatter.InstanceProvidingMember == ".ctor" + ? base.GetFormatterInstanceForResolver() + : $"{this.GetFormatterNameForResolver()}.{this.CustomFormatter.InstanceProvidingMember}"; + } +} diff --git a/src/MessagePack.SourceGenerator/CodeAnalysis/EnumSerializationInfo.cs b/src/MessagePack.SourceGenerator/CodeAnalysis/EnumSerializationInfo.cs new file mode 100644 index 000000000..10ad9ce71 --- /dev/null +++ b/src/MessagePack.SourceGenerator/CodeAnalysis/EnumSerializationInfo.cs @@ -0,0 +1,35 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; + +namespace MessagePack.SourceGenerator.CodeAnalysis; + +public sealed record EnumSerializationInfo : ResolverRegisterInfo +{ + public required string UnderlyingTypeName { get; init; } + + public string UnderlyingTypeKeyword => this.UnderlyingTypeName switch + { + "SByte" => "sbyte", + "Byte" => "byte", + "Int16" => "short", + "UInt16" => "ushort", + "Int32" => "int", + "UInt32" => "uint", + "Int64" => "long", + "UInt64" => "ulong", + _ => this.UnderlyingTypeName, + }; + + public static EnumSerializationInfo Create(INamedTypeSymbol dataType, ISymbol enumUnderlyingType, ResolverOptions resolverOptions) + { + ResolverRegisterInfo basicInfo = ResolverRegisterInfo.Create(dataType, resolverOptions); + return new EnumSerializationInfo + { + DataType = basicInfo.DataType, + Formatter = basicInfo.Formatter, + UnderlyingTypeName = enumUnderlyingType.ToDisplayString(BinaryWriteFormat), + }; + } +} diff --git a/src/MessagePack.SourceGenerator/CodeAnalysis/FullModel.cs b/src/MessagePack.SourceGenerator/CodeAnalysis/FullModel.cs new file mode 100644 index 000000000..7610e176a --- /dev/null +++ b/src/MessagePack.SourceGenerator/CodeAnalysis/FullModel.cs @@ -0,0 +1,143 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; + +namespace MessagePack.SourceGenerator.CodeAnalysis; + +public record FullModel( + ImmutableSortedSet ObjectInfos, + ImmutableSortedSet EnumInfos, + ImmutableSortedSet GenericInfos, + ImmutableSortedSet UnionInfos, + ImmutableSortedSet CustomFormatterInfos, + ImmutableSortedSet ArrayFormatterInfos, + AnalyzerOptions Options) +{ + public static readonly FullModel Empty = new( + ImmutableSortedSet.Create(ResolverRegisterInfoComparer.Default), + ImmutableSortedSet.Create(ResolverRegisterInfoComparer.Default), + ImmutableSortedSet.Create(ResolverRegisterInfoComparer.Default), + ImmutableSortedSet.Create(ResolverRegisterInfoComparer.Default), + ImmutableSortedSet.Create(ResolverRegisterInfoComparer.Default), + ImmutableSortedSet.Create(ResolverRegisterInfoComparer.Default), + new AnalyzerOptions()); + + public bool IsEmpty => this.ObjectInfos.IsEmpty && this.EnumInfos.IsEmpty && this.GenericInfos.IsEmpty && this.UnionInfos.IsEmpty && this.CustomFormatterInfos.IsEmpty; + + /// + /// Returns a new model that contains all the content of a collection of models. + /// + /// The models to be combined. + /// The new, combined model. + /// Thrown if is not equal between any two models. + public static FullModel Combine(ImmutableArray models) + { + if (models.Length == 0) + { + return Empty; + } + + AnalyzerOptions options = models[0].Options; + var objectInfos = ImmutableSortedSet.CreateBuilder(ResolverRegisterInfoComparer.Default); + var enumInfos = ImmutableSortedSet.CreateBuilder(ResolverRegisterInfoComparer.Default); + var genericInfos = ImmutableSortedSet.CreateBuilder(ResolverRegisterInfoComparer.Default); + var unionInfos = ImmutableSortedSet.CreateBuilder(ResolverRegisterInfoComparer.Default); + var customFormatterInfos = ImmutableSortedSet.CreateBuilder(ResolverRegisterInfoComparer.Default); + var arrayFormatterInfos = ImmutableSortedSet.CreateBuilder(ResolverRegisterInfoComparer.Default); + + foreach (FullModel model in models) + { + objectInfos.UnionWith(model.ObjectInfos); + enumInfos.UnionWith(model.EnumInfos); + genericInfos.UnionWith(model.GenericInfos); + unionInfos.UnionWith(model.UnionInfos); + customFormatterInfos.UnionWith(model.CustomFormatterInfos); + arrayFormatterInfos.UnionWith(model.ArrayFormatterInfos); + + if (options != model.Options) + { + throw new NotSupportedException("Options must be equal."); + } + } + + return new FullModel( + objectInfos.ToImmutable(), + enumInfos.ToImmutable(), + genericInfos.ToImmutable(), + unionInfos.ToImmutable(), + customFormatterInfos.ToImmutable(), + arrayFormatterInfos.ToImmutable(), + options); + } + + public virtual bool Equals(FullModel? other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (this.GetType() != other.GetType()) + { + return false; + } + + return ObjectInfos.SequenceEqual(other.ObjectInfos) + && EnumInfos.SequenceEqual(other.EnumInfos) + && GenericInfos.SequenceEqual(other.GenericInfos) + && UnionInfos.SequenceEqual(other.UnionInfos) + && CustomFormatterInfos.SequenceEqual(other.CustomFormatterInfos) + && Options.Equals(other.Options); + } + + public override int GetHashCode() + { + int hashCode = 0; + + hashCode = Hash(hashCode, this.ObjectInfos.Count); + if (this.ObjectInfos.Count > 0) + { + hashCode = Hash(hashCode, this.ObjectInfos[0].GetHashCode()); + } + + hashCode = Hash(hashCode, this.EnumInfos.Count); + if (this.EnumInfos.Count > 0) + { + hashCode = Hash(hashCode, this.EnumInfos[0].GetHashCode()); + } + + hashCode = Hash(hashCode, this.GenericInfos.Count); + if (this.GenericInfos.Count > 0) + { + hashCode = Hash(hashCode, this.GenericInfos[0].GetHashCode()); + } + + hashCode = Hash(hashCode, this.UnionInfos.Count); + if (this.UnionInfos.Count > 0) + { + hashCode = Hash(hashCode, this.UnionInfos[0].GetHashCode()); + } + + hashCode = Hash(hashCode, this.CustomFormatterInfos.Count); + if (this.CustomFormatterInfos.Count > 0) + { + hashCode = Hash(hashCode, this.CustomFormatterInfos[0].GetHashCode()); + } + + hashCode = Hash(hashCode, this.Options.GetHashCode()); + + return hashCode; + + static int Hash(int hashCode, int value) + { + return (hashCode * 31) + value; + } + } +} diff --git a/src/MessagePack.SourceGenerator/CodeAnalysis/GenericSerializationInfo.cs b/src/MessagePack.SourceGenerator/CodeAnalysis/GenericSerializationInfo.cs new file mode 100644 index 000000000..33f1a90cd --- /dev/null +++ b/src/MessagePack.SourceGenerator/CodeAnalysis/GenericSerializationInfo.cs @@ -0,0 +1,26 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; + +namespace MessagePack.SourceGenerator.CodeAnalysis; + +/// +/// Describes a constructed generic type (one that has known type arguments) +/// that must be serializable. +/// +public sealed record GenericSerializationInfo : ResolverRegisterInfo +{ + public override bool IsUnboundGenericType => false; + + public static new GenericSerializationInfo Create(INamedTypeSymbol dataType, ResolverOptions resolverOptions, FormatterPosition formatterLocation = FormatterPosition.UnderResolver) + { + ResolverRegisterInfo basicInfo = ResolverRegisterInfo.Create(dataType, resolverOptions, formatterLocation); + return new GenericSerializationInfo + { + DataType = basicInfo.DataType, + Formatter = basicInfo.Formatter, + }; + } +} diff --git a/src/MessagePack.SourceGenerator/CodeAnalysis/GenericTypeParameterInfo.cs b/src/MessagePack.SourceGenerator/CodeAnalysis/GenericTypeParameterInfo.cs new file mode 100644 index 000000000..c35fe171a --- /dev/null +++ b/src/MessagePack.SourceGenerator/CodeAnalysis/GenericTypeParameterInfo.cs @@ -0,0 +1,91 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Immutable; +using System.Text; +using Microsoft.CodeAnalysis; + +namespace MessagePack.SourceGenerator.CodeAnalysis; + +public record GenericTypeParameterInfo(string Name) : IComparable +{ + private string? constraints; + + private GenericTypeParameterInfo(ITypeParameterSymbol typeParameter, ImmutableStack recursionGuard) + : this(typeParameter.Name) + { + recursionGuard = recursionGuard.Push(this); + + this.HasUnmanagedTypeConstraint = typeParameter.HasUnmanagedTypeConstraint; + this.HasReferenceTypeConstraint = typeParameter.HasReferenceTypeConstraint; + this.HasValueTypeConstraint = typeParameter.HasValueTypeConstraint; + this.HasNotNullConstraint = typeParameter.HasNotNullConstraint; + this.ConstraintTypes = typeParameter.ConstraintTypes.Select(t => QualifiedTypeName.Create(t, recursionGuard)).ToImmutableEquatableArray(); + this.HasConstructorConstraint = typeParameter.HasConstructorConstraint; + this.ReferenceTypeConstraintNullableAnnotation = typeParameter.ReferenceTypeConstraintNullableAnnotation; + } + + public static GenericTypeParameterInfo Create(ITypeParameterSymbol typeParameter, ImmutableStack? recursionGuard = null) + { + if (recursionGuard?.FirstOrDefault(tpi => tpi.Name == typeParameter.Name) is { } existing) + { + return existing; + } + + return new(typeParameter, recursionGuard ?? ImmutableStack.Empty); + } + + public string Constraints => this.constraints ??= this.ConstructConstraintString(); + + public bool HasConstraints => this.Constraints.Length > 0; + + public bool HasUnmanagedTypeConstraint { get; init; } + + public bool HasReferenceTypeConstraint { get; init; } + + public NullableAnnotation ReferenceTypeConstraintNullableAnnotation { get; init; } + + public bool HasValueTypeConstraint { get; init; } + + public bool HasNotNullConstraint { get; init; } + + public ImmutableEquatableArray ConstraintTypes { get; init; } = ImmutableEquatableArray.Empty; + + public bool HasConstructorConstraint { get; init; } + + public int CompareTo(GenericTypeParameterInfo other) => this.Name.CompareTo(other.Name); + + private string ConstructConstraintString() + { + StringBuilder builder = new(); + + // `notnull`, `unmanaged`, `class`, `struct` constraint must come before any constraints. + AddIf(this.HasNotNullConstraint, "notnull"); + AddIf(this.HasReferenceTypeConstraint, this.ReferenceTypeConstraintNullableAnnotation == NullableAnnotation.Annotated ? "class?" : "class"); + AddIf(this.HasValueTypeConstraint, this.HasUnmanagedTypeConstraint ? "unmanaged" : "struct"); + + // constraint types (IDisposable, IEnumerable ...) + foreach (QualifiedNamedTypeName constraintType in this.ConstraintTypes) + { + AddIf(true, constraintType.GetQualifiedName(genericStyle: GenericParameterStyle.Arguments, includeNullableAnnotation: true)); + } + + // `new()` constraint must be last in constraints. + AddIf(this.HasConstructorConstraint, "new()"); + + void AddIf(bool condition, string constraint) + { + if (condition) + { + if (builder.Length > 0) + { + builder.Append(", "); + } + + builder.Append(constraint); + } + } + + return builder.ToString(); + } +} diff --git a/src/MessagePack.SourceGenerator/CodeAnalysis/MemberSerializationInfo.cs b/src/MessagePack.SourceGenerator/CodeAnalysis/MemberSerializationInfo.cs new file mode 100644 index 000000000..ad3d3d302 --- /dev/null +++ b/src/MessagePack.SourceGenerator/CodeAnalysis/MemberSerializationInfo.cs @@ -0,0 +1,74 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace MessagePack.SourceGenerator.CodeAnalysis; + +public record MemberSerializationInfo( + bool IsProperty, + bool IsWritable, + bool IsReadable, + bool IsInitOnly, + bool IsRequired, + int IntKey, + string StringKey, + string Name, + string Type, + string ShortTypeName, + FormatterDescriptor? CustomFormatter) +{ + private static readonly IReadOnlyCollection PrimitiveTypes = new HashSet(AnalyzerUtilities.PrimitiveTypes); + + public string LocalVariableName => $"__{this.UniqueIdentifier}__"; + + public string UniqueIdentifier => this.DeclaringType is null ? this.Name : $"{this.DeclaringType.Name}_{this.Name}"; + + /// + /// Gets the declaring type of this member, if a derived type declares a new member + /// with the same identifier, thus requiring a source generator to cast + /// the target to the member's declaring type in order to access it. + /// + public required QualifiedNamedTypeName? DeclaringType { get; init; } + + public string GetSerializeMethodString() + { + string memberRead = this.GetMemberAccess("value"); + + if (CustomFormatter is not null) + { + return $"this.__{this.Name}CustomFormatter__.Serialize(ref writer, {memberRead}, options)"; + } + else if (PrimitiveTypes.Contains(this.Type)) + { + return "writer.Write(value." + this.Name + ")"; + } + else + { + return $"MsgPack::FormatterResolverExtensions.GetFormatterWithVerify<{this.Type}>(formatterResolver).Serialize(ref writer, {memberRead}, options)"; + } + } + + public string GetDeserializeMethodString() + { + if (CustomFormatter is not null) + { + return $"this.__{this.Name}CustomFormatter__.Deserialize(ref reader, options)"; + } + else if (PrimitiveTypes.Contains(this.Type)) + { + if (this.Type == "byte[]") + { + return "MsgPack::Internal.CodeGenHelpers.GetArrayFromNullableSequence(reader.ReadBytes())"; + } + else + { + return $"reader.Read{this.ShortTypeName!.Replace("[]", "s")}()"; + } + } + else + { + return $"MsgPack::FormatterResolverExtensions.GetFormatterWithVerify<{this.Type}>(formatterResolver).Deserialize(ref reader, options)"; + } + } + + public string GetMemberAccess(string targetObject) => this.DeclaringType is null ? $"{targetObject}.{this.Name}" : $"(({this.DeclaringType.GetQualifiedName()}){targetObject}).{this.Name}"; +} diff --git a/src/MessagePack.SourceGenerator/CodeAnalysis/ObjectSerializationInfo.cs b/src/MessagePack.SourceGenerator/CodeAnalysis/ObjectSerializationInfo.cs new file mode 100644 index 000000000..f1b4e762f --- /dev/null +++ b/src/MessagePack.SourceGenerator/CodeAnalysis/ObjectSerializationInfo.cs @@ -0,0 +1,185 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Text; +using Microsoft.CodeAnalysis; + +namespace MessagePack.SourceGenerator.CodeAnalysis; + +public record ObjectSerializationInfo : ResolverRegisterInfo +{ + public required bool IsClass { get; init; } + + public required GenericTypeParameterInfo[] GenericTypeParameters { get; init; } + + public required MemberSerializationInfo[] ConstructorParameters { get; init; } + + /// + /// Gets the members that are either init-only properties or required (and therefore must appear in an object initializer). + /// + public required MemberSerializationInfo[] InitMembers { get; init; } + + public bool MustDeserializeFieldsFirst => this.ConstructorParameters.Length > 0 || this.InitMembers.Length > 0; + + public required bool IsIntKey { get; init; } + + public required MemberSerializationInfo[] Members { get; init; } + + public required bool HasIMessagePackSerializationCallbackReceiver { get; init; } + + public required bool NeedsCastOnAfter { get; init; } + + public required bool NeedsCastOnBefore { get; init; } + + public bool IsStringKey => !this.IsIntKey; + + public int WriteCount + { + get + { + if (this.IsStringKey) + { + return this.Members.Count(x => x.IsReadable); + } + else + { + return this.MaxKey; + } + } + } + + public int MaxKey + { + get + { + return this.Members.Where(x => x.IsReadable).Select(x => x.IntKey).DefaultIfEmpty(-1).Max(); + } + } + + public static ObjectSerializationInfo Create( + INamedTypeSymbol dataType, + bool isClass, + bool nestedFormatterRequired, + GenericTypeParameterInfo[] genericTypeParameters, + MemberSerializationInfo[] constructorParameters, + bool isIntKey, + MemberSerializationInfo[] members, + bool hasIMessagePackSerializationCallbackReceiver, + bool needsCastOnAfter, + bool needsCastOnBefore, + ResolverOptions resolverOptions) + { + ResolverRegisterInfo basicInfo = ResolverRegisterInfo.Create( + dataType, + resolverOptions, + nestedFormatterRequired ? FormatterPosition.UnderDataType : FormatterPosition.UnderResolver); + return new ObjectSerializationInfo + { + DataType = basicInfo.DataType, + Formatter = basicInfo.Formatter, + IsClass = isClass, + GenericTypeParameters = genericTypeParameters, + ConstructorParameters = constructorParameters, + InitMembers = members.Where(x => ((x.IsProperty && x.IsInitOnly) || x.IsRequired) && !constructorParameters.Contains(x)).ToArray(), + IsIntKey = isIntKey, + Members = members, + HasIMessagePackSerializationCallbackReceiver = hasIMessagePackSerializationCallbackReceiver, + NeedsCastOnAfter = needsCastOnAfter, + NeedsCastOnBefore = needsCastOnBefore, + }; + } + + public MemberSerializationInfo? GetMember(int index) + { + return this.Members.FirstOrDefault(x => x.IntKey == index); + } + + public string GetConstructorString() + { + StringBuilder builder = new(); + builder.Append(this.DataType.GetQualifiedName(Qualifiers.GlobalNamespace, GenericParameterStyle.Identifiers)); + + builder.Append('('); + + for (int i = 0; i < this.ConstructorParameters.Length; i++) + { + if (i != 0) + { + builder.Append(", "); + } + + builder.Append(this.ConstructorParameters[i].LocalVariableName); + } + + builder.Append(')'); + + if (this.InitMembers.Length > 0) + { + builder.Append(" { "); + + for (int i = 0; i < this.InitMembers.Length; i++) + { + if (i != 0) + { + builder.Append(", "); + } + + // Strictly speaking, we should only be assigning these init-only properties if values for them + // was provided in the deserialized stream. + // However the C# language does not provide a means to do this, so we always assign them. + // https://github.com/dotnet/csharplang/issues/6117 + builder.Append(this.InitMembers[i].Name); + builder.Append(" = "); + builder.Append(this.InitMembers[i].LocalVariableName); + } + + builder.Append(" }"); + } + + return builder.ToString(); + } + + public virtual bool Equals(ObjectSerializationInfo? other) + { + if (other == null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + if (this.GetType() != other.GetType()) + { + return false; + } + + // Compare all the properties by value + return base.Equals(other) + && IsClass == other.IsClass + && GenericTypeParameters.SequenceEqual(other.GenericTypeParameters) + && ConstructorParameters.SequenceEqual(other.ConstructorParameters) + && IsIntKey == other.IsIntKey + && Members.SequenceEqual(other.Members) + && HasIMessagePackSerializationCallbackReceiver == other.HasIMessagePackSerializationCallbackReceiver + && NeedsCastOnAfter == other.NeedsCastOnAfter + && NeedsCastOnBefore == other.NeedsCastOnBefore; + } + + public override int GetHashCode() + { + int hash = base.GetHashCode(); + + hash = Hash(hash, this.IsClass ? 1 : 2); + hash = Hash(hash, this.GenericTypeParameters.Length); + + return hash; + + static int Hash(int hashCode, int value) + { + return (hashCode * 31) + value; + } + } +} diff --git a/src/MessagePack.SourceGenerator/CodeAnalysis/QualifiedArrayTypeName.cs b/src/MessagePack.SourceGenerator/CodeAnalysis/QualifiedArrayTypeName.cs new file mode 100644 index 000000000..f41f9c8e9 --- /dev/null +++ b/src/MessagePack.SourceGenerator/CodeAnalysis/QualifiedArrayTypeName.cs @@ -0,0 +1,93 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Text; +using Microsoft.CodeAnalysis; + +namespace MessagePack.SourceGenerator.CodeAnalysis; + +/// +/// Describes an array type by its name and qualifiers. +/// +[DebuggerDisplay($"{{{nameof(DebuggerDisplay)},nq}}")] +public record QualifiedArrayTypeName : QualifiedTypeName, IComparable +{ + private int arrayRank; + + public QualifiedArrayTypeName() + { + } + + [SetsRequiredMembers] + public QualifiedArrayTypeName(IArrayTypeSymbol symbol) + { + this.ArrayRank = symbol.Rank; + this.ElementType = Create(symbol.ElementType); + this.ReferenceTypeNullableAnnotation = symbol.NullableAnnotation; + } + + public required int ArrayRank + { + get => this.arrayRank; + init + { + if (value <= 0) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + this.arrayRank = value; + } + } + + public required QualifiedTypeName ElementType { get; init; } + + public override TypeKind Kind + { + get => TypeKind.Array; + + init + { + if (value != TypeKind.Array) + { + throw new NotSupportedException(); + } + } + } + + public override bool IsRecord => false; + + public int CompareTo(QualifiedArrayTypeName other) + { + int compare = this.ArrayRank.CompareTo(other.ArrayRank); + if (compare != 0) + { + return compare; + } + + return this.ElementType.CompareTo(other.ElementType); + } + + public override string ToString() => this.GetQualifiedName(Qualifiers.Namespace, GenericParameterStyle.Arguments); + + public override string GetQualifiedName(Qualifiers qualifier = Qualifiers.GlobalNamespace, GenericParameterStyle genericStyle = GenericParameterStyle.Identifiers, bool includeNullableAnnotation = false) + { + StringBuilder builder = new(); + + builder.Append(this.ElementType.GetQualifiedName(qualifier, genericStyle, includeNullableAnnotation)); + + Debug.Assert(this.ArrayRank > 0, "ElementType is not null === ArrayRank > 0"); + builder.Append('['); + builder.Append(',', this.ArrayRank - 1); + builder.Append(']'); + + if (includeNullableAnnotation && this.ReferenceTypeNullableAnnotation == NullableAnnotation.Annotated) + { + builder.Append("?"); + } + + return builder.ToString(); + } +} diff --git a/src/MessagePack.SourceGenerator/CodeAnalysis/QualifiedNamedTypeName.cs b/src/MessagePack.SourceGenerator/CodeAnalysis/QualifiedNamedTypeName.cs new file mode 100644 index 000000000..dafbc33ed --- /dev/null +++ b/src/MessagePack.SourceGenerator/CodeAnalysis/QualifiedNamedTypeName.cs @@ -0,0 +1,289 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma warning disable SA1402 // File may only contain a single type + +using System.Collections.Immutable; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Text; +using Microsoft.CodeAnalysis; + +namespace MessagePack.SourceGenerator.CodeAnalysis; + +/// +/// Describes a type (that is not an array, nor nested type) by its name and qualifiers. +/// +[DebuggerDisplay($"{{{nameof(DebuggerDisplay)},nq}}")] +public record QualifiedNamedTypeName : QualifiedTypeName, IComparable +{ + private readonly TypeKind kind; + private readonly bool isRecord; + + public QualifiedNamedTypeName(TypeKind kind, bool isRecord = false) + { + if (kind == TypeKind.Array) + { + throw new ArgumentException($"Create an {nameof(QualifiedArrayTypeName)} instead.", nameof(kind)); + } + + if (isRecord && kind is not TypeKind.Class or TypeKind.Struct) + { + throw new ArgumentException("Cannot be a record if kind is not a class or struct.", nameof(isRecord)); + } + + this.kind = kind; + this.isRecord = isRecord; + } + + [SetsRequiredMembers] + public QualifiedNamedTypeName(INamedTypeSymbol symbol, ImmutableStack? recursionGuard = null) + { + this.Name = symbol.Name; + this.Kind = symbol.TypeKind; + this.isRecord |= symbol.IsRecord; + this.Container = symbol.ContainingType is { } nesting ? new NestingTypeContainer(new(nesting, recursionGuard)) : + symbol.ContainingNamespace?.GetFullNamespaceName() is string ns ? new NamespaceTypeContainer(ns) : + null; + this.TypeParameters = CodeAnalysisUtilities.GetTypeParameters(symbol, recursionGuard); + this.TypeArguments = CodeAnalysisUtilities.GetTypeArguments(symbol, recursionGuard); + this.ReferenceTypeNullableAnnotation = symbol.NullableAnnotation; + this.AccessModifier = symbol.DeclaredAccessibility; + } + + [SetsRequiredMembers] + public QualifiedNamedTypeName(ITypeParameterSymbol symbol) + { + this.Name = symbol.Name; + this.Kind = TypeKind.TypeParameter; + this.ReferenceTypeNullableAnnotation = symbol.ReferenceTypeConstraintNullableAnnotation; + } + + /// + /// Gets the containing namespace or nesting type of this type. + /// + public TypeContainer? Container { get; init; } + + /// + /// Gets the simple name of the type (unqualified by namespace or containing types), + /// and without any generic type parameters. + /// + public required string Name { get; init; } + + public override TypeKind Kind + { + get => this.kind; + + init + { + if (kind == TypeKind.Array) + { + throw new ArgumentException($"Create an {nameof(QualifiedArrayTypeName)} instead.", nameof(kind)); + } + + this.kind = value; + } + } + + public override bool IsRecord => this.isRecord; + + /// + /// Gets the access modifier that should accompany any declaration of this type, if any. + /// + public Accessibility? AccessModifier { get; init; } + + /// + /// Gets the generic type parameters that belong to the type (e.g. T, T2). + /// + public ImmutableArray TypeParameters { get; init; } = ImmutableArray.Empty; + + /// + /// Gets the generic type arguments that may be present to close a generic type (e.g. string). + /// + public ImmutableArray TypeArguments { get; init; } = ImmutableArray.Empty; + + /// + /// Gets the number of generic type parameters that belong to the type. + /// + public int Arity => this.TypeParameters.Length; + + public override int GetHashCode() => this.Name.GetHashCode(); + + public virtual bool Equals(QualifiedNamedTypeName? other) + { + if (other is not QualifiedNamedTypeName typedOther) + { + return false; + } + + return base.Equals(other) + && this.Container == typedOther.Container + && this.Name == typedOther.Name + && this.AccessModifier == typedOther.AccessModifier + && this.TypeParameters.SequenceEqual(typedOther.TypeParameters); + } + + public override string ToString() => this.GetQualifiedName(Qualifiers.Namespace, GenericParameterStyle.Arguments); + + public int CompareTo(QualifiedNamedTypeName other) + { + int result = Comparer.Default.Compare(this.Container, other.Container); + if (result != 0) + { + return result; + } + + result = StringComparer.Ordinal.Compare(this.Kind, other.Kind); + if (result != 0) + { + return result; + } + + result = StringComparer.Ordinal.Compare(this.Name, other.Name); + if (result != 0) + { + return result; + } + + result = this.TypeParameters.Length.CompareTo(other.TypeParameters.Length); + if (result != 0) + { + return result; + } + + for (int i = 0; i < this.TypeParameters.Length; i++) + { + result = this.TypeParameters[i].CompareTo(other.TypeParameters[i]); + if (result != 0) + { + return result; + } + } + + for (int i = 0; i < this.TypeArguments.Length; i++) + { + result = this.TypeArguments[i].CompareTo(other.TypeArguments[i]); + if (result != 0) + { + return result; + } + } + + return 0; + } + + public override string GetQualifiedName(Qualifiers qualifier = Qualifiers.GlobalNamespace, GenericParameterStyle genericStyle = GenericParameterStyle.Identifiers, bool includeNullableAnnotation = false) + { + // Optimize for C# keywords and syntax sugar. + if (this.Container is NamespaceTypeContainer { Namespace: "System" }) + { + switch (this.Name) + { + case "UInt64": return "ulong"; + case "UInt32": return "uint"; + case "UInt16": return "ushort"; + case "Single": return "float"; + case "Double": return "double"; + case "Decimal": return "decimal"; + case "Boolean": return "bool"; + case "Byte": return "byte"; + case "Int64": return "long"; + case "Int32": return "int"; + case "Int16": return "short"; + case "SByte": return "sbyte"; + case "Char": return "char"; + case "String": return "string"; + case "Object": return "object"; + case "ValueTuple" when this.TypeArguments.Length > 1: return $"({string.Join(", ", this.TypeArguments.Select(ta => ta.GetQualifiedName(qualifier, genericStyle, includeNullableAnnotation)))})"; + case "Nullable": return $"{this.TypeArguments[0].GetQualifiedName(qualifier, genericStyle, includeNullableAnnotation)}?"; + } + } + + StringBuilder builder = new(); + + switch (this.Container) + { + case NestingTypeContainer { NestingType: { } nesting }: + if (qualifier >= Qualifiers.ContainingTypes) + { + builder.Append(nesting.GetQualifiedName(qualifier, genericStyle, includeNullableAnnotation)); + builder.Append('.'); + } + + break; + default: + string? ns = (this.Container as NamespaceTypeContainer)?.Namespace; + + // Only add the global:: prefix if an alias prefix is not already present. + if (qualifier is Qualifiers.GlobalNamespace && ns?.Contains("::") is not true) + { + builder.Append("global::"); + } + + if (ns is not null && qualifier >= Qualifiers.Namespace) + { + builder.Append(ns); + builder.Append('.'); + } + + break; + } + + builder.Append(this.Name); + builder.Append(this.GetTypeParametersOrArgs(genericStyle)); + + if (includeNullableAnnotation && this.ReferenceTypeNullableAnnotation == NullableAnnotation.Annotated) + { + builder.Append("?"); + } + + return builder.ToString(); + } + + public override string GetTypeParametersOrArgs(GenericParameterStyle style) + { + return style switch + { + GenericParameterStyle.Identifiers when !this.TypeParameters.IsEmpty => $"<{string.Join(", ", this.TypeParameters.Select(tp => tp.Name))}>", + GenericParameterStyle.Arguments when !this.TypeArguments.IsEmpty => $"<{string.Join(", ", this.TypeArguments.Select(ta => ta.GetQualifiedName(genericStyle: GenericParameterStyle.Arguments, includeNullableAnnotation: true)))}>", + GenericParameterStyle.TypeDefinition when this.Arity > 0 => $"<{new string(',', this.Arity - 1)}>", + GenericParameterStyle.ArityOnly when this.Arity > 0 => $"`{this.Arity}", + _ => string.Empty, + }; + } +} + +public abstract record TypeContainer : IComparable +{ + public abstract int CompareTo(TypeContainer? other); +} + +[DebuggerDisplay($"{{{nameof(NestingType)},nq}}+")] +public record NestingTypeContainer(QualifiedNamedTypeName NestingType) : TypeContainer +{ + public static NestingTypeContainer? From(QualifiedNamedTypeName? nesting) => nesting is null ? null : new(nesting); + + public override string ToString() => $"{this.NestingType}+"; + + public override int CompareTo(TypeContainer? other) + { + return other is NestingTypeContainer nestingOther ? this.NestingType.CompareTo(nestingOther.NestingType) : + other is NamespaceTypeContainer ? -1 : // nested types come before namespace types + 1; // nested types come after non-qualified types. + } +} + +[DebuggerDisplay($"{{{nameof(Namespace)},nq}}.")] +public record NamespaceTypeContainer(string Namespace) : TypeContainer +{ + public static NamespaceTypeContainer? From(string? ns) => ns is null ? null : new(ns); + + public override string ToString() => $"{this.Namespace}."; + + public override int CompareTo(TypeContainer? other) + { + return other is NamespaceTypeContainer nsOther ? this.Namespace.CompareTo(nsOther.Namespace) : + other is NestingTypeContainer ? 1 : // namespace types come after nested ones + 1; // namespace types comes after non-qualified types + } +} diff --git a/src/MessagePack.SourceGenerator/CodeAnalysis/QualifiedTypeName.cs b/src/MessagePack.SourceGenerator/CodeAnalysis/QualifiedTypeName.cs new file mode 100644 index 000000000..5eda4f6cf --- /dev/null +++ b/src/MessagePack.SourceGenerator/CodeAnalysis/QualifiedTypeName.cs @@ -0,0 +1,146 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; + +namespace MessagePack.SourceGenerator.CodeAnalysis; + +/// +/// Describes a type by its name and qualifiers. +/// +public abstract record QualifiedTypeName : IComparable +{ + /// + /// Initializes a new instance of the class. + /// + /// The symbol this instance will identify. + /// A stack of objects that serves to avoid infinite recursion when generic constraints are written in terms of themselves. + public static QualifiedTypeName Create(ITypeSymbol symbol, ImmutableStack? recursionGuard = null) + { + return symbol switch + { + IArrayTypeSymbol arrayType => new QualifiedArrayTypeName(arrayType), + INamedTypeSymbol namedType => new QualifiedNamedTypeName(namedType, recursionGuard), + ITypeParameterSymbol typeParam => new TypeParameter(typeParam), + _ => throw new NotSupportedException(), + }; + } + + /// + public static QualifiedTypeName Create(ITypeSymbol symbol) => Create(symbol, null); + + /// + /// Gets the kind of type. + /// + public abstract TypeKind Kind { get; init; } + + /// + /// Gets a value indicating whether the type is a record type. + /// + public abstract bool IsRecord { get; } + + public NullableAnnotation ReferenceTypeNullableAnnotation { get; init; } + + protected string DebuggerDisplay => this.GetQualifiedName(Qualifiers.Namespace, GenericParameterStyle.Arguments); + + public abstract string GetQualifiedName(Qualifiers qualifier = Qualifiers.GlobalNamespace, GenericParameterStyle genericStyle = GenericParameterStyle.Identifiers, bool includeNullableAnnotation = false); + + /// + /// Builds a string that acts as a suffix for a C# type name to represent the generic type parameters. + /// + /// The generic type parameters style to generate. + /// A string to append to a generic type name, or an empty string if arity is 0. + public virtual string GetTypeParametersOrArgs(GenericParameterStyle style) => string.Empty; + + public int CompareTo(QualifiedTypeName other) => Compare(this, other); + + public override string ToString() => this.GetQualifiedName(Qualifiers.Namespace); + + protected int Compare(QualifiedTypeName? left, QualifiedTypeName? right) + { + if (left is null && right is null) + { + return 0; + } + + if (left is null) + { + return -1; + } + + if (right is null) + { + return 1; + } + + // Sort array types before named types. + if (left is QualifiedArrayTypeName leftArray) + { + return right is QualifiedArrayTypeName rightArray ? leftArray.CompareTo(rightArray) : -1; + } + else if (left is QualifiedNamedTypeName leftNamed) + { + return right is QualifiedNamedTypeName rightNamed ? leftNamed.CompareTo(rightNamed) : 1; + } + else if (left is TypeParameter leftTypeParameter) + { + return right is TypeParameter rightTypeParameter ? leftTypeParameter.CompareTo(rightTypeParameter) : 1; + } + else + { + throw new NotImplementedException(); + } + } +} + +public enum GenericParameterStyle +{ + /// + /// No generic type parameters will be included at all. + /// + None, + + /// + /// The generic type definition suffix (lacking type identifiers, such as e.g. <,> for a 2-arity generic type). + /// + TypeDefinition, + + /// + /// The most common suffix that includes type identifiers (e.g. <T1, T2>). + /// + Identifiers, + + /// + /// The original type arguments (e.g. <string, int>). + /// + Arguments, + + /// + /// A suffix that only indicates arity of the generic type. e.g. `1 or `2. + /// + ArityOnly, +} + +public enum Qualifiers +{ + /// + /// No qualifiers. The type name will stand alone. + /// + None, + + /// + /// Includes nesting types, if any. + /// + ContainingTypes, + + /// + /// The type name will be qualified by the full namespace (without global::). + /// + Namespace, + + /// + /// The type name will be qualified by the full namespace and leading global::. + /// + GlobalNamespace, +} diff --git a/src/MessagePack.SourceGenerator/CodeAnalysis/ReferenceSymbols.cs b/src/MessagePack.SourceGenerator/CodeAnalysis/ReferenceSymbols.cs new file mode 100644 index 000000000..31e92a3d8 --- /dev/null +++ b/src/MessagePack.SourceGenerator/CodeAnalysis/ReferenceSymbols.cs @@ -0,0 +1,85 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; + +namespace MessagePack.SourceGenerator.CodeAnalysis; + +public record ReferenceSymbols( + INamedTypeSymbol MessagePackObjectAttribute, + INamedTypeSymbol UnionAttribute, + INamedTypeSymbol SerializationConstructorAttribute, + INamedTypeSymbol KeyAttribute, + INamedTypeSymbol IgnoreAttribute, + INamedTypeSymbol FormatterAttribute, + INamedTypeSymbol? MessagePackFormatter, + INamedTypeSymbol? MessagePackFormatterOfT, + INamedTypeSymbol? IgnoreDataMemberAttribute, + INamedTypeSymbol IMessagePackSerializationCallbackReceiver) +{ + public static bool TryCreate(Compilation compilation, [NotNullWhen(true)] out ReferenceSymbols? instance) + { + instance = null; + + INamedTypeSymbol? messagePackObjectAttribute = compilation.GetTypeByMetadataName("MessagePack.MessagePackObjectAttribute"); + if (messagePackObjectAttribute is null) + { + return false; + } + + INamedTypeSymbol? unionAttribute = compilation.GetTypeByMetadataName("MessagePack.UnionAttribute"); + if (unionAttribute is null) + { + return false; + } + + INamedTypeSymbol? serializationConstructor = compilation.GetTypeByMetadataName("MessagePack.SerializationConstructorAttribute"); + if (serializationConstructor is null) + { + return false; + } + + INamedTypeSymbol? keyAttribute = compilation.GetTypeByMetadataName("MessagePack.KeyAttribute"); + if (keyAttribute is null) + { + return false; + } + + INamedTypeSymbol? ignoreAttribute = compilation.GetTypeByMetadataName("MessagePack.IgnoreMemberAttribute"); + if (ignoreAttribute is null) + { + return false; + } + + INamedTypeSymbol? formatterAttribute = compilation.GetTypeByMetadataName("MessagePack.MessagePackFormatterAttribute"); + if (formatterAttribute is null) + { + return false; + } + + INamedTypeSymbol? messageFormatter = compilation.GetTypeByMetadataName("MessagePack.Formatters.IMessagePackFormatter"); + INamedTypeSymbol? messageFormatterOfT = compilation.GetTypeByMetadataName("MessagePack.Formatters.IMessagePackFormatter`1")?.ConstructUnboundGenericType(); + + INamedTypeSymbol? ignoreDataMemberAttribute = compilation.GetTypeByMetadataName("System.Runtime.Serialization.IgnoreDataMemberAttribute"); + + INamedTypeSymbol? messagePackSerializationCallbackReceiver = compilation.GetTypeByMetadataName("MessagePack.IMessagePackSerializationCallbackReceiver"); + if (messagePackSerializationCallbackReceiver is null) + { + return false; + } + + instance = new ReferenceSymbols( + messagePackObjectAttribute, + unionAttribute, + serializationConstructor, + keyAttribute, + ignoreAttribute, + formatterAttribute, + messageFormatter, + messageFormatterOfT, + ignoreDataMemberAttribute, + messagePackSerializationCallbackReceiver); + return true; + } +} diff --git a/src/MessagePack.SourceGenerator/CodeAnalysis/ResolverRegisterInfo.cs b/src/MessagePack.SourceGenerator/CodeAnalysis/ResolverRegisterInfo.cs new file mode 100644 index 000000000..12d60e2ad --- /dev/null +++ b/src/MessagePack.SourceGenerator/CodeAnalysis/ResolverRegisterInfo.cs @@ -0,0 +1,150 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; + +namespace MessagePack.SourceGenerator.CodeAnalysis; + +public record ResolverRegisterInfo +{ + protected static readonly SymbolDisplayFormat ShortTypeNameFormat = new SymbolDisplayFormat( + typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypes); + + protected static readonly SymbolDisplayFormat BinaryWriteFormat = new SymbolDisplayFormat( + genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters, + miscellaneousOptions: SymbolDisplayMiscellaneousOptions.ExpandNullable, + typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameOnly); + + /// + /// Creates formatter and resolver info for a data type that should be serializable. + /// + /// The type to be serialized. + /// The resolver options. + /// Position the formatter under the data type when it must access private members. + /// The resolver and formatter information. + public static ResolverRegisterInfo Create(INamedTypeSymbol dataType, ResolverOptions resolverOptions, FormatterPosition formatterLocation = FormatterPosition.UnderResolver) + { + QualifiedNamedTypeName dataTypeName = new(dataType); + + return new ResolverRegisterInfo + { + FormatterLocation = formatterLocation, + DataType = dataTypeName, + Formatter = new QualifiedNamedTypeName(TypeKind.Class) + { + Container = NestingTypeContainer.From(formatterLocation switch + { + FormatterPosition.UnderResolver => GetNestingTypeForFormatterUnderResolver(dataTypeName, resolverOptions), + FormatterPosition.UnderDataType => dataTypeName, + _ => throw new NotImplementedException(), + }), + Name = $"{dataTypeName.Name}Formatter", + Kind = TypeKind.Class, + AccessModifier = dataTypeName.AccessModifier, + TypeParameters = formatterLocation == FormatterPosition.UnderDataType ? ImmutableArray.Empty : dataTypeName.TypeParameters, + TypeArguments = formatterLocation == FormatterPosition.UnderDataType ? ImmutableArray.Empty : dataTypeName.TypeArguments, + }, + }; + } + + /// + /// Creates formatter and resolver info for an array data type that should be serializable. + /// + /// The type to be serialized. + /// The resolver options. + /// Position the formatter under the data type when it must access private members. + /// The resolver and formatter information. + public static ResolverRegisterInfo CreateArray(IArrayTypeSymbol dataType, ResolverOptions resolverOptions, FormatterPosition formatterLocation = FormatterPosition.UnderResolver) + { + QualifiedArrayTypeName dataTypeName = new(dataType); + + return new ResolverRegisterInfo + { + FormatterLocation = formatterLocation, + DataType = dataTypeName, + Formatter = new QualifiedNamedTypeName(TypeKind.Class) + { + Name = $"XXXFormatter", + }, + }; + } + + /// + /// Gets the name of the type to be formatted. + /// + public required QualifiedTypeName DataType { get; init; } + + /// + /// Gets the name of the formatter. + /// + public required QualifiedNamedTypeName Formatter { get; init; } + + /// + /// Gets the declaration parent of the formatter. + /// + /// + /// The formatter is always a nested class of something else (the resolver or the data type). + /// If the formatter is nested under the resolver, the namespaces of the data type become nested types + /// under which the formatter is declared. + /// + public FormatterPosition FormatterLocation { get; init; } + + public virtual string GetFormatterNameForResolver(GenericParameterStyle style = GenericParameterStyle.Identifiers) => this.Formatter.GetQualifiedName(Qualifiers.GlobalNamespace, style); + + /// + /// Gets the C# expression that will provide an instance of the formatter. + /// + public virtual string GetFormatterInstanceForResolver() => $"new {this.GetFormatterNameForResolver(GenericParameterStyle.Arguments)}()"; + + /// + /// Gets a value indicating whether this data type is a generic type with unknown type arguments. + /// + public virtual bool IsUnboundGenericType => this.DataType is QualifiedNamedTypeName { Arity: > 0 }; + + public string FileNameHint => $"Formatters.{this.Formatter.GetQualifiedName(Qualifiers.Namespace, GenericParameterStyle.ArityOnly)}"; + + private static QualifiedNamedTypeName? GetNestingTypeForFormatterUnderResolver(QualifiedTypeName dataType, ResolverOptions resolverOptions) + { + // Each nesting type of the data type become a nested type for the formatter. + if (dataType is QualifiedNamedTypeName { Container: NestingTypeContainer { NestingType: { } nestingType } }) + { + // The data type aready has a nesting type. Reusing that for the formatter, but recurse in + // because ultimately we need to add the namespaces as nesting types, and then add the resolver + // as the outermost type and finally the namespace the resolver appears in. + return nestingType with + { + Container = GetNestingTypeForFormatterUnderResolver(nestingType, resolverOptions) is { } n ? new NestingTypeContainer(n) : null, + AccessModifier = Accessibility.Internal, + }; + } + + // Each namespace of the data type also becomes a nesting type of the formatter. + if (dataType is QualifiedNamedTypeName { Container: NamespaceTypeContainer { Namespace: string ns } }) + { + string[] namespaces = ns.Split('.'); + QualifiedNamedTypeName? partialClassAsNamespaceStep = resolverOptions.Name; + for (int i = 0; i < namespaces.Length; i++) + { + partialClassAsNamespaceStep = new(TypeKind.Class) + { + Name = namespaces[i], + Container = new NestingTypeContainer(partialClassAsNamespaceStep), + AccessModifier = Accessibility.Internal, + }; + } + + return partialClassAsNamespaceStep; + } + + return resolverOptions.Name; + } + + public override int GetHashCode() => this.DataType.GetHashCode(); + + public enum FormatterPosition + { + UnderResolver, + UnderDataType, + } +} diff --git a/src/MessagePack.SourceGenerator/CodeAnalysis/ResolverRegisterInfoComparer.cs b/src/MessagePack.SourceGenerator/CodeAnalysis/ResolverRegisterInfoComparer.cs new file mode 100644 index 000000000..9d05c9d72 --- /dev/null +++ b/src/MessagePack.SourceGenerator/CodeAnalysis/ResolverRegisterInfoComparer.cs @@ -0,0 +1,15 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace MessagePack.SourceGenerator.CodeAnalysis; + +public class ResolverRegisterInfoComparer : IComparer +{ + public static readonly ResolverRegisterInfoComparer Default = new(); + + private ResolverRegisterInfoComparer() + { + } + + public int Compare(ResolverRegisterInfo x, ResolverRegisterInfo y) => x.DataType.CompareTo(y.DataType); +} diff --git a/src/MessagePack.SourceGenerator/CodeAnalysis/TypeCollector.cs b/src/MessagePack.SourceGenerator/CodeAnalysis/TypeCollector.cs new file mode 100644 index 000000000..f8150e02a --- /dev/null +++ b/src/MessagePack.SourceGenerator/CodeAnalysis/TypeCollector.cs @@ -0,0 +1,1508 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma warning disable SA1402 // File may only contain a single type +#pragma warning disable SA1649 // File name should match first type name + +using System.Collections.Immutable; +using System.Reflection; +using System.Text.RegularExpressions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace MessagePack.SourceGenerator.CodeAnalysis; + +public class MessagePackGeneratorResolveFailedException : Exception +{ + public MessagePackGeneratorResolveFailedException(string message) + : base(message) + { + } +} + +public class TypeCollector +{ + private static readonly SymbolDisplayFormat BinaryWriteFormat = new SymbolDisplayFormat( + genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters, + miscellaneousOptions: SymbolDisplayMiscellaneousOptions.ExpandNullable, + typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameOnly); + + private static readonly SymbolDisplayFormat ShortTypeNameFormat = new SymbolDisplayFormat( + typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypes); + + private static readonly HashSet EmbeddedTypes = new(new[] + { + "short", + "int", + "long", + "ushort", + "uint", + "ulong", + "float", + "double", + "bool", + "byte", + "sbyte", + "decimal", + "char", + "string", + "object", + "System.Guid", + "System.TimeSpan", + "System.DateTime", + "System.DateTimeOffset", + + "MessagePack.Nil", + + // and arrays + "short[]", + "int[]", + "long[]", + "ushort[]", + "uint[]", + "ulong[]", + "float[]", + "double[]", + "bool[]", + "byte[]", + "sbyte[]", + "decimal[]", + "char[]", + "string[]", + "object[]", + "System.DateTime[]", + "System.ArraySegment", + "System.Memory", + "System.ReadOnlyMemory", + "System.Buffers.ReadOnlySequence", + + // List + "System.Collections.Generic.List", + "System.Collections.Generic.List", + "System.Collections.Generic.List", + "System.Collections.Generic.List", + "System.Collections.Generic.List", + "System.Collections.Generic.List", + "System.Collections.Generic.List", + "System.Collections.Generic.List", + "System.Collections.Generic.List", + "System.Collections.Generic.List", + "System.Collections.Generic.List", + "System.Collections.Generic.List", + "System.Collections.Generic.List", + "System.Collections.Generic.List", + "System.Collections.Generic.List", + + // extensions + "UnityEngine.Vector2", + "UnityEngine.Vector3", + "UnityEngine.Vector4", + "UnityEngine.Quaternion", + "UnityEngine.Color", + "UnityEngine.Bounds", + "UnityEngine.Rect", + "UnityEngine.AnimationCurve", + "UnityEngine.RectOffset", + "UnityEngine.Gradient", + "UnityEngine.WrapMode", + "UnityEngine.GradientMode", + "UnityEngine.Keyframe", + "UnityEngine.Matrix4x4", + "UnityEngine.GradientColorKey", + "UnityEngine.GradientAlphaKey", + "UnityEngine.Color32", + "UnityEngine.LayerMask", + "UnityEngine.Vector2Int", + "UnityEngine.Vector3Int", + "UnityEngine.RangeInt", + "UnityEngine.RectInt", + "UnityEngine.BoundsInt", + + "System.Reactive.Unit", + + "System.Numerics.BigInteger", + "System.Numerics.Complex", + "System.Numerics.Vector2", + "System.Numerics.Vector3", + "System.Numerics.Vector4", + "System.Numerics.Quaternion", + "System.Numerics.Matrix3x2", + "System.Numerics.Matrix4x4", + }); + + private static readonly Dictionary KnownGenericTypes = new() + { +#pragma warning disable SA1509 // Opening braces should not be preceded by blank line + { "System.Collections.Generic.List<>", "MsgPack::Formatters.ListFormatter" }, + { "System.Collections.Generic.LinkedList<>", "MsgPack::Formatters.LinkedListFormatter" }, + { "System.Collections.Generic.Queue<>", "MsgPack::Formatters.QueueFormatter" }, + { "System.Collections.Generic.Stack<>", "MsgPack::Formatters.StackFormatter" }, + { "System.Collections.Generic.HashSet<>", "MsgPack::Formatters.HashSetFormatter" }, + { "System.Collections.ObjectModel.ReadOnlyCollection<>", "MsgPack::Formatters.ReadOnlyCollectionFormatter" }, + { "System.Collections.Generic.IList<>", "MsgPack::Formatters.InterfaceListFormatter2" }, + { "System.Collections.Generic.ICollection<>", "MsgPack::Formatters.InterfaceCollectionFormatter2" }, + { "System.Collections.Generic.IEnumerable<>", "MsgPack::Formatters.InterfaceEnumerableFormatter" }, + { "System.Collections.Generic.Dictionary<,>", "MsgPack::Formatters.DictionaryFormatter" }, + { "System.Collections.Generic.IDictionary<,>", "MsgPack::Formatters.InterfaceDictionaryFormatter" }, + { "System.Collections.Generic.SortedDictionary<,>", "MsgPack::Formatters.SortedDictionaryFormatter" }, + { "System.Collections.Generic.SortedList<,>", "MsgPack::Formatters.SortedListFormatter" }, + { "System.Linq.ILookup<,>", "MsgPack::Formatters.InterfaceLookupFormatter" }, + { "System.Linq.IGrouping<,>", "MsgPack::Formatters.InterfaceGroupingFormatter" }, + { "System.Collections.ObjectModel.ObservableCollection<>", "MsgPack::Formatters.ObservableCollectionFormatter" }, + { "System.Collections.ObjectModel.ReadOnlyObservableCollection<>", "MsgPack::Formatters.ReadOnlyObservableCollectionFormatter" }, + { "System.Collections.Generic.IReadOnlyList<>", "MsgPack::Formatters.InterfaceReadOnlyListFormatter" }, + { "System.Collections.Generic.IReadOnlyCollection<>", "MsgPack::Formatters.InterfaceReadOnlyCollectionFormatter" }, + { "System.Collections.Generic.ISet<>", "MsgPack::Formatters.InterfaceSetFormatter" }, + { "System.Collections.Concurrent.ConcurrentBag<>", "MsgPack::Formatters.ConcurrentBagFormatter" }, + { "System.Collections.Concurrent.ConcurrentQueue<>", "MsgPack::Formatters.ConcurrentQueueFormatter" }, + { "System.Collections.Concurrent.ConcurrentStack<>", "MsgPack::Formatters.ConcurrentStackFormatter" }, + { "System.Collections.ObjectModel.ReadOnlyDictionary<,>", "MsgPack::Formatters.ReadOnlyDictionaryFormatter" }, + { "System.Collections.Generic.IReadOnlyDictionary<,>", "MsgPack::Formatters.InterfaceReadOnlyDictionaryFormatter" }, + { "System.Collections.Concurrent.ConcurrentDictionary<,>", "MsgPack::Formatters.ConcurrentDictionaryFormatter" }, + // NET5 + { "System.Collections.Generic.IReadOnlySet<>", "MsgPack::Formatters.InterfaceReadOnlySetFormatter" }, + // NET6 + { "System.Collections.Generic.PriorityQueue<,>", "MsgPack::Formatters.PriorityQueueFormatter" }, + // NET9 + { "System.Collections.Generic.OrderedDictionary<,>", "MsgPack::Formatters.OrderedDictionaryFormatter" }, + { "System.Collections.ObjectModel.ReadOnlySet<>", "MsgPack::Formatters.ReadOnlySetFormatter" }, + + { "System.Lazy<>", "MsgPack::Formatters.LazyFormatter" }, + { "System.Threading.Tasks<>", "MsgPack::Formatters.TaskValueFormatter" }, + + { "System.Tuple<>", "MsgPack::Formatters.TupleFormatter" }, + { "System.Tuple<,>", "MsgPack::Formatters.TupleFormatter" }, + { "System.Tuple<,,>", "MsgPack::Formatters.TupleFormatter" }, + { "System.Tuple<,,,>", "MsgPack::Formatters.TupleFormatter" }, + { "System.Tuple<,,,,>", "MsgPack::Formatters.TupleFormatter" }, + { "System.Tuple<,,,,,>", "MsgPack::Formatters.TupleFormatter" }, + { "System.Tuple<,,,,,,>", "MsgPack::Formatters.TupleFormatter" }, + { "System.Tuple<,,,,,,,>", "MsgPack::Formatters.TupleFormatter" }, + + { "System.ValueTuple<>", "MsgPack::Formatters.ValueTupleFormatter" }, + { "System.ValueTuple<,>", "MsgPack::Formatters.ValueTupleFormatter" }, + { "System.ValueTuple<,,>", "MsgPack::Formatters.ValueTupleFormatter" }, + { "System.ValueTuple<,,,>", "MsgPack::Formatters.ValueTupleFormatter" }, + { "System.ValueTuple<,,,,>", "MsgPack::Formatters.ValueTupleFormatter" }, + { "System.ValueTuple<,,,,,>", "MsgPack::Formatters.ValueTupleFormatter" }, + { "System.ValueTuple<,,,,,,>", "MsgPack::Formatters.ValueTupleFormatter" }, + { "System.ValueTuple<,,,,,,,>", "MsgPack::Formatters.ValueTupleFormatter" }, + + { "System.Collections.Generic.KeyValuePair<,>", "MsgPack::Formatters.KeyValuePairFormatter" }, + { "System.Threading.Tasks.ValueTask<>", "MsgPack::Formatters.KeyValuePairFormatter" }, + { "System.ArraySegment<>", "MsgPack::Formatters.ArraySegmentFormatter" }, + { "System.Memory<>", "MsgPack::Formatters.MemoryFormatter" }, + { "System.ReadOnlyMemory<>", "MsgPack::Formatters.ReadOnlyMemoryFormatter" }, + { "System.Buffers.ReadOnlySequence<>", "MsgPack::Formatters.ReadOnlySequenceFormatter" }, + + // extensions + { "System.Collections.Immutable.ImmutableArray<>", "MsgPack::ImmutableCollection.ImmutableArrayFormatter" }, + { "System.Collections.Immutable.ImmutableList<>", "MsgPack::ImmutableCollection.ImmutableListFormatter" }, + { "System.Collections.Immutable.ImmutableDictionary<,>", "MsgPack::ImmutableCollection.ImmutableDictionaryFormatter" }, + { "System.Collections.Immutable.ImmutableHashSet<>", "MsgPack::ImmutableCollection.ImmutableHashSetFormatter" }, + { "System.Collections.Immutable.ImmutableSortedDictionary<,>", "MsgPack::ImmutableCollection.ImmutableSortedDictionaryFormatter" }, + { "System.Collections.Immutable.ImmutableSortedSet<>", "MsgPack::ImmutableCollection.ImmutableSortedSetFormatter" }, + { "System.Collections.Immutable.ImmutableQueue<>", "MsgPack::ImmutableCollection.ImmutableQueueFormatter" }, + { "System.Collections.Immutable.ImmutableStack<>", "MsgPack::ImmutableCollection.ImmutableStackFormatter" }, + { "System.Collections.Immutable.IImmutableList<>", "MsgPack::ImmutableCollection.InterfaceImmutableListFormatter" }, + { "System.Collections.Immutable.IImmutableDictionary<,>", "MsgPack::ImmutableCollection.InterfaceImmutableDictionaryFormatter" }, + { "System.Collections.Immutable.IImmutableQueue<>", "MsgPack::ImmutableCollection.InterfaceImmutableQueueFormatter" }, + { "System.Collections.Immutable.IImmutableSet<>", "MsgPack::ImmutableCollection.InterfaceImmutableSetFormatter" }, + { "System.Collections.Immutable.IImmutableStack<>", "MsgPack::ImmutableCollection.InterfaceImmutableStackFormatter" }, + + { "Reactive.Bindings.ReactiveProperty<>", "MsgPack::ReactivePropertyExtension.ReactivePropertyFormatter" }, + { "Reactive.Bindings.IReactiveProperty<>", "MsgPack::ReactivePropertyExtension.InterfaceReactivePropertyFormatter" }, + { "Reactive.Bindings.IReadOnlyReactiveProperty<>", "MsgPack::ReactivePropertyExtension.InterfaceReadOnlyReactivePropertyFormatter" }, + { "Reactive.Bindings.ReactiveCollection<>", "MsgPack::ReactivePropertyExtension.ReactiveCollectionFormatter" }, +#pragma warning restore SA1509 // Opening braces should not be preceded by blank line + }; + + private readonly AnalyzerOptions options; + private readonly ReferenceSymbols typeReferences; + + /// + /// The means of reporting diagnostics to the analyzer. + /// This will be when running in the context of a source generator so as to avoid duplicate diagnostics. + /// + private readonly Action? reportDiagnostic; + + private readonly ITypeSymbol? targetType; + + // visitor workspace: + private readonly HashSet alreadyCollected = new(SymbolEqualityComparer.Default); + private readonly ImmutableSortedSet.Builder collectedObjectInfo = ImmutableSortedSet.CreateBuilder(ResolverRegisterInfoComparer.Default); + private readonly ImmutableSortedSet.Builder collectedEnumInfo = ImmutableSortedSet.CreateBuilder(ResolverRegisterInfoComparer.Default); + private readonly ImmutableSortedSet.Builder collectedGenericInfo = ImmutableSortedSet.CreateBuilder(ResolverRegisterInfoComparer.Default); + private readonly ImmutableSortedSet.Builder collectedUnionInfo = ImmutableSortedSet.CreateBuilder(ResolverRegisterInfoComparer.Default); + private readonly ImmutableSortedSet.Builder collectedArrayInfo = ImmutableSortedSet.CreateBuilder(ResolverRegisterInfoComparer.Default); + + private readonly Compilation compilation; + + private readonly CancellationToken cancellationToken; + + private TypeCollector(Compilation compilation, AnalyzerOptions options, ReferenceSymbols referenceSymbols, ITypeSymbol targetType, Action? reportAnalyzerDiagnostic, CancellationToken cancellationToken) + { + this.typeReferences = referenceSymbols; + this.reportDiagnostic = reportAnalyzerDiagnostic; + this.options = options; + this.compilation = compilation; + this.cancellationToken = cancellationToken; + + bool isInaccessible = false; + foreach (BaseTypeDeclarationSyntax? decl in CodeAnalysisUtilities.FindInaccessibleTypes(targetType)) + { + isInaccessible = true; + reportAnalyzerDiagnostic?.Invoke(Diagnostic.Create(MsgPack00xMessagePackAnalyzer.InaccessibleDataType, decl.Identifier.GetLocation())); + } + + if (!isInaccessible) + { + if (((targetType.TypeKind == TypeKind.Interface) && targetType.GetAttributes().Any(x2 => x2.AttributeClass.ApproximatelyEqual(this.typeReferences.UnionAttribute))) + || ((targetType.TypeKind == TypeKind.Class && targetType.IsAbstract) && targetType.GetAttributes().Any(x2 => x2.AttributeClass.ApproximatelyEqual(this.typeReferences.UnionAttribute))) + || ((targetType.TypeKind == TypeKind.Class) && targetType.GetAttributes().Any(x2 => x2.AttributeClass.ApproximatelyEqual(this.typeReferences.MessagePackObjectAttribute))) + || ((targetType.TypeKind == TypeKind.Struct) && targetType.GetAttributes().Any(x2 => x2.AttributeClass.ApproximatelyEqual(this.typeReferences.MessagePackObjectAttribute)))) + { + this.targetType = targetType; + } + } + } + + public static FullModel? Collect(Compilation compilation, AnalyzerOptions options, ReferenceSymbols referenceSymbols, Action? reportAnalyzerDiagnostic, ITypeSymbol targetType, CancellationToken cancellationToken) + { + TypeCollector collector = new(compilation, options, referenceSymbols, targetType, reportAnalyzerDiagnostic, cancellationToken); + if (collector.targetType is null) + { + return null; + } + + FullModel model = collector.Collect(); + return model; + } + + private void ResetWorkspace() + { + this.alreadyCollected.Clear(); + this.collectedObjectInfo.Clear(); + this.collectedEnumInfo.Clear(); + this.collectedGenericInfo.Clear(); + this.collectedUnionInfo.Clear(); + this.collectedArrayInfo.Clear(); + } + + // EntryPoint + public FullModel Collect() + { + this.ResetWorkspace(); + + if (this.targetType is not null) + { + this.CollectCore(this.targetType); + } + + return new FullModel( + this.collectedObjectInfo.ToImmutable(), + this.collectedEnumInfo.ToImmutable(), + this.collectedGenericInfo.ToImmutable(), + this.collectedUnionInfo.ToImmutable(), + ImmutableSortedSet.Empty, + this.collectedArrayInfo.ToImmutable(), + this.options); + } + + // Gate of recursive collect + private void CollectCore(ITypeSymbol typeSymbol, ISymbol? callerSymbol = null) + { + this.cancellationToken.ThrowIfCancellationRequested(); + + if (!this.alreadyCollected.Add(typeSymbol)) + { + return; + } + + var typeSymbolString = typeSymbol.WithNullableAnnotation(NullableAnnotation.NotAnnotated).ToString(); + if (!string.IsNullOrEmpty(typeSymbolString) && EmbeddedTypes.Contains(typeSymbolString)) + { + return; + } + + FormattableType formattableType = new(typeSymbol, null); + if (this.options.AssumedFormattableTypes.Contains(formattableType) || this.options.AssumedFormattableTypes.Contains(formattableType with { IsFormatterInSameAssembly = true })) + { + return; + } + + if (typeSymbol is IArrayTypeSymbol arrayTypeSymbol) + { + this.CollectArray((IArrayTypeSymbol)this.ToTupleUnderlyingType(arrayTypeSymbol), callerSymbol); + return; + } + + if (typeSymbol is ITypeParameterSymbol) + { + return; + } + + if (!IsAllowAccessibility(typeSymbol)) + { + return; + } + + if (!(typeSymbol is INamedTypeSymbol type)) + { + return; + } + + var customFormatterAttr = typeSymbol.GetAttributes().FirstOrDefault(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.FormatterAttribute)); + if (customFormatterAttr != null) + { + this.CheckValidMessagePackFormatterAttribute(customFormatterAttr); + return; + } + + if (type.EnumUnderlyingType != null) + { + this.CollectEnum(type, type.EnumUnderlyingType); + return; + } + + if (type.IsGenericType) + { + this.CollectGeneric((INamedTypeSymbol)this.ToTupleUnderlyingType(type), callerSymbol); + return; + } + + if (type.Locations[0].IsInMetadata) + { + return; + } + + if (type.TypeKind == TypeKind.Interface || (type.TypeKind == TypeKind.Class && type.IsAbstract)) + { + this.CollectUnion(type); + return; + } + + this.CollectObject(type, callerSymbol); + } + + private void CollectEnum(INamedTypeSymbol type, ISymbol enumUnderlyingType) + { + this.collectedEnumInfo.Add(EnumSerializationInfo.Create(type, enumUnderlyingType, this.options.Generator.Resolver)); + } + + private void CollectUnion(INamedTypeSymbol type) + { + ImmutableArray[] unionAttrs = type.GetAttributes().Where(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.UnionAttribute)).Select(x => x.ConstructorArguments).ToArray(); + if (unionAttrs.Length == 0) + { + this.reportDiagnostic?.Invoke(Diagnostic.Create(MsgPack00xMessagePackAnalyzer.UnionAttributeRequired, type.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax().GetLocation())); + return; + } + + // 0, Int 1, SubType + UnionSubTypeInfo? UnionSubTypeInfoSelector(ImmutableArray x) + { + if (!(x[0] is { Value: int key }) || !(x[1] is { Value: ITypeSymbol typeSymbol })) + { + this.reportDiagnostic?.Invoke(Diagnostic.Create(MsgPack00xMessagePackAnalyzer.AotUnionAttributeRequiresTypeArg, GetIdentifierLocation(type))); + return null; + } + + CollectCore(typeSymbol); + + var typeName = typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + return new UnionSubTypeInfo(key, typeName); + } + + var subTypes = unionAttrs.Select(UnionSubTypeInfoSelector).Where(i => i is not null).OrderBy(x => x!.Key).ToImmutableArray(); + + if (!options.IsGeneratingSource) + { + return; + } + + var info = UnionSerializationInfo.Create( + type, + subTypes!, + this.options.Generator.Resolver); + + this.collectedUnionInfo.Add(info); + } + + private void CollectGenericUnion(INamedTypeSymbol type) + { + var unionAttrs = type.GetAttributes().Where(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.UnionAttribute)).Select(x => x.ConstructorArguments); + using var enumerator = unionAttrs.GetEnumerator(); + if (!enumerator.MoveNext()) + { + return; + } + + do + { + var x = enumerator.Current; + if (x[1] is { Value: INamedTypeSymbol unionType } && !this.alreadyCollected.Contains(unionType)) + { + this.CollectCore(unionType); + } + } + while (enumerator.MoveNext()); + } + + private void CollectArray(IArrayTypeSymbol array, ISymbol? callerSymbol) + { + ITypeSymbol elemType = array.ElementType; + + this.CollectCore(elemType, callerSymbol); + + if (elemType is ITypeParameterSymbol || (elemType is INamedTypeSymbol symbol && IsOpenGenericTypeRecursively(symbol))) + { + return; + } + + QualifiedTypeName elementTypeName = QualifiedTypeName.Create(elemType); + string? formatterName = array.Rank switch + { + 1 => "ArrayFormatter", + 2 => "TwoDimensionalArrayFormatter", + 3 => "ThreeDimensionalArrayFormatter", + 4 => "FourDimensionalArrayFormatter", + _ => null, + }; + if (formatterName is null) + { + ////this.reportDiagnostic?.Invoke(Diagnostic.Create(MsgPack00xMessagePackAnalyzer.AotArrayRankTooHigh)); + return; + } + + var info = ResolverRegisterInfo.CreateArray(array, this.options.Generator.Resolver) with + { + Formatter = new QualifiedNamedTypeName(TypeKind.Class) + { + Container = new NamespaceTypeContainer("MsgPack::Formatters"), + Name = formatterName, + TypeParameters = ImmutableArray.Create(new GenericTypeParameterInfo("T")), + TypeArguments = ImmutableArray.Create(elementTypeName), + }, + }; + this.collectedArrayInfo.Add(info); + } + + private ITypeSymbol ToTupleUnderlyingType(ITypeSymbol typeSymbol) + { + if (typeSymbol is IArrayTypeSymbol array) + { + return this.compilation.CreateArrayTypeSymbol(this.ToTupleUnderlyingType(array.ElementType), array.Rank); + } + + if (typeSymbol is not INamedTypeSymbol namedType || !namedType.IsGenericType) + { + return typeSymbol; + } + + namedType = namedType.TupleUnderlyingType ?? namedType; + var newTypeArguments = namedType.TypeArguments.Select(this.ToTupleUnderlyingType).ToArray(); + if (!namedType.TypeArguments.SequenceEqual(newTypeArguments)) + { + return namedType.ConstructedFrom.Construct(newTypeArguments); + } + + return namedType; + } + + private void CollectGeneric(INamedTypeSymbol type, ISymbol? callerSymbol) + { + INamedTypeSymbol typeDefinition = type.ConstructUnboundGenericType(); + var genericTypeDefinitionString = typeDefinition.ToDisplayString(); + string? genericTypeNamespace = type.ContainingNamespace?.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat); + var fullName = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + var isOpenGenericType = IsOpenGenericTypeRecursively(type); + string formattedTypeFullName = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + string? formatterName; + + // nullable + if (genericTypeDefinitionString == "T?") + { + var firstTypeArgument = type.TypeArguments[0]; + + if (EmbeddedTypes.Contains(firstTypeArgument.ToString()!)) + { + return; + } + + this.CollectCore(firstTypeArgument, callerSymbol); + + if (!isOpenGenericType) + { + formatterName = $"NullableFormatter<{firstTypeArgument.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>"; + var info = GenericSerializationInfo.Create(type, this.options.Generator.Resolver) with + { + Formatter = new QualifiedNamedTypeName(TypeKind.Class) + { + Name = formatterName, + Container = new NamespaceTypeContainer("MsgPack::Formatters"), + }, + }; + this.collectedGenericInfo.Add(info); + } + + return; + } + + // collection + string? formatterFullName; + if (KnownGenericTypes.TryGetValue(genericTypeDefinitionString, out formatterFullName)) + { + foreach (ITypeSymbol item in type.TypeArguments) + { + this.CollectCore(item, callerSymbol); + } + + GenericSerializationInfo info; + + if (isOpenGenericType) + { + return; + } + else + { + info = GenericSerializationInfo.Create(type, this.options.Generator.Resolver); + int indexOfLastPeriod = formatterFullName.LastIndexOf('.'); + info = info with + { + Formatter = new(TypeKind.Class) + { + Container = new NamespaceTypeContainer(formatterFullName.Substring(0, indexOfLastPeriod)), + Name = formatterFullName.Substring(indexOfLastPeriod + 1), + TypeParameters = GetTypeParameters(info.DataType), + TypeArguments = GetTypeArguments(info.DataType), + }, + }; + + this.collectedGenericInfo.Add(info); + } + + if (genericTypeDefinitionString != "System.Linq.ILookup<,>") + { + return; + } + + formatterName = KnownGenericTypes["System.Linq.IGrouping<,>"]; + + var groupingInfo = GenericSerializationInfo.Create(type, this.options.Generator.Resolver); + groupingInfo = groupingInfo with + { + DataType = new QualifiedNamedTypeName(TypeKind.Interface) + { + Container = new NamespaceTypeContainer("global::System.Linq"), + Name = "IGrouping", + TypeParameters = GetTypeParameters(info.DataType), + }, + Formatter = groupingInfo.Formatter with { Name = formatterName }, + }; + this.collectedGenericInfo.Add(groupingInfo); + + formatterName = KnownGenericTypes["System.Collections.Generic.IEnumerable<>"]; + + GenericSerializationInfo enumerableInfo = GenericSerializationInfo.Create(type, this.options.Generator.Resolver); + enumerableInfo = enumerableInfo with + { + DataType = new QualifiedNamedTypeName(TypeKind.Interface) + { + Container = new NamespaceTypeContainer("System.Collections.Generic"), + Name = "IEnumerable", + TypeParameters = ImmutableArray.Create(GetTypeParameters(info.DataType)[1]), + }, + Formatter = enumerableInfo.Formatter with { Name = formatterName }, + }; + this.collectedGenericInfo.Add(enumerableInfo); + return; + } + + if (type.AllInterfaces.FirstOrDefault(x => x.IsGenericType && !x.IsUnboundGenericType && x.ConstructUnboundGenericType() is { Name: nameof(ICollection) }) is INamedTypeSymbol collectionIface + && type.InstanceConstructors.Any(ctor => ctor.Parameters.Length == 0)) + { + this.CollectCore(collectionIface.TypeArguments[0], callerSymbol); + + if (isOpenGenericType) + { + return; + } + else + { + GenericSerializationInfo info = GenericSerializationInfo.Create(type, this.options.Generator.Resolver) with + { + Formatter = new(TypeKind.Class) + { + Container = new NamespaceTypeContainer("MsgPack::Formatters"), + Name = "GenericCollectionFormatter", + TypeParameters = ImmutableArray.Create(new("TElement"), new("TCollection")), + TypeArguments = ImmutableArray.Create(QualifiedTypeName.Create(collectionIface.TypeArguments[0]), QualifiedTypeName.Create(type)), + }, + }; + + this.collectedGenericInfo.Add(info); + return; + } + } + + // Generic types + if (type.IsDefinition) + { + this.CollectGenericUnion(type); + this.CollectObject(type, callerSymbol); + } + else + { + // Collect substituted types for the properties and fields. + // NOTE: It is used to register formatters from nested generic type. + // However, closed generic types such as `Foo` are not registered as a formatter. + this.GetObjectInfo(type, callerSymbol); + + // Collect generic type definition, that is not collected when it is defined outside target project. + this.CollectCore(type.OriginalDefinition, callerSymbol); + } + + // Collect substituted types for the type parameters (e.g. Bar in Foo) + foreach (var item in type.TypeArguments) + { + this.CollectCore(item, callerSymbol); + } + + if (!isOpenGenericType) + { + GenericSerializationInfo genericSerializationInfo = GenericSerializationInfo.Create(type, this.options.Generator.Resolver); + this.collectedGenericInfo.Add(genericSerializationInfo); + } + + static ImmutableArray GetTypeParameters(QualifiedTypeName qtn) + => qtn is QualifiedNamedTypeName named ? named.TypeParameters : ImmutableArray.Empty; + + static ImmutableArray GetTypeArguments(QualifiedTypeName qtn) + => qtn is QualifiedNamedTypeName named ? named.TypeArguments : ImmutableArray.Empty; + } + + private void CollectObject(INamedTypeSymbol type, ISymbol? callerSymbol) + { + ObjectSerializationInfo? info = this.GetObjectInfo(type, callerSymbol); + if (info is not null) + { + this.collectedObjectInfo.Add(info); + } + } + + private void CheckValidMessagePackFormatterAttribute(AttributeData formatterAttribute) + { + if (formatterAttribute.ConstructorArguments[0].Value is ITypeSymbol formatterType) + { + // Validate that the typed formatter is actually of `IMessagePackFormatter` + bool isMessagePackFormatter = formatterType.AllInterfaces.Any(x => SymbolEqualityComparer.Default.Equals(x, this.typeReferences.MessagePackFormatter)); + if (!isMessagePackFormatter) + { + Location? location = ((AttributeSyntax?)formatterAttribute.ApplicationSyntaxReference?.GetSyntax())?.ArgumentList?.Arguments[0].GetLocation(); + ImmutableDictionary typeInfo = ImmutableDictionary.Create().Add("type", formatterType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); + this.reportDiagnostic?.Invoke(Diagnostic.Create(MsgPack00xMessagePackAnalyzer.MessageFormatterMustBeMessagePackFormatter, location, typeInfo)); + } + } + } + + private ObjectSerializationInfo? GetObjectInfo(INamedTypeSymbol formattedType, ISymbol? callerSymbol) + { + var isClass = !formattedType.IsValueType; + bool nestedFormatterRequired = false; + List nestedFormatterRequiredIfPropertyIsNotSetByDeserializingCtor = new(); + + AttributeData? contractAttr = formattedType.GetAttributes().FirstOrDefault(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.MessagePackObjectAttribute)); + + // Examine properties set on the attribute such that we can discern whether they were explicitly set or not. + // This is useful when we have assembly-level attributes or other environmentally-controlled defaults that the attribute may override either direction. + bool? suppressSourceGeneration = (bool?)contractAttr?.NamedArguments.FirstOrDefault(kvp => kvp.Key == Constants.SuppressSourceGenerationPropertyName).Value.Value; + + // Do not source generate the formatter for this type if the attribute opted out. + if (suppressSourceGeneration is true) + { + // Skip any source generation + return null; + } + + bool? allowPrivateAttribute = (bool?)contractAttr?.NamedArguments.FirstOrDefault(kvp => kvp.Key == Constants.AllowPrivatePropertyName).Value.Value; + + if (contractAttr is null) + { + if (this.reportDiagnostic != null) + { + var diagnostics = Diagnostic.Create(MsgPack00xMessagePackAnalyzer.TypeMustBeMessagePackObject, ((BaseTypeDeclarationSyntax)formattedType.DeclaringSyntaxReferences[0].GetSyntax()).Identifier.GetLocation(), formattedType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); + this.reportDiagnostic.Invoke(diagnostics); + } + + return null; + } + + bool isIntKey = true; + Dictionary intMembers = new(); + Dictionary stringMembers = new(); + + // We default to not requiring non-public members to be attributed, + // but as soon as the first non-public member *is* attributed, + // we'll require all non-public members to be attributed. + // Historically, only public members were able to be serialized by default, + // so the analyzer only highlighted missing attributes on public members. + // But once it's clear that the developer is serializing non-public members, + // we need to raise the bar for all non-public members. + bool nonPublicMembersAreSerialized = false; + List deferredDiagnosticsForNonPublicMembers = new(); + + FormatterDescriptor? GetSpecialFormatter(ISymbol member) + { + INamedTypeSymbol? name = member.GetAttributes().FirstOrDefault(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.FormatterAttribute))?.ConstructorArguments[0].Value as INamedTypeSymbol; + if (name is not null) + { + if (FormatterDescriptor.TryCreate(name, out FormatterDescriptor? descriptor)) + { + return descriptor; + } + else + { + // Report that the named type is not a formatter. + // this.reportDiagnostic(); + } + } + + return null; + } + + HashSet reportedDiagnostics = new(); + IEnumerable instanceMembers = formattedType.GetAllMembers() + .Where(m => m is IFieldSymbol or IPropertySymbol && !(m.IsStatic || m.IsOverride || m.IsImplicitlyDeclared)); + + // The actual member identifiers of each serializable member, + // such that we'll recognize collisions as we enumerate members in base types + // and record that that happened so that the emitted source generator can apply the necessary casts. + // In particular this does NOT store substitute names given by KeyAttribute.Name. + // Members should be added to this set after they are verified to be accessible, instance, non-override properties, + // whether or not those members are serialized. + HashSet collidingMemberNames = new(StringComparer.Ordinal); + HashSet observedMemberNames = new(StringComparer.Ordinal); + foreach (ISymbol member in instanceMembers) + { + if (!observedMemberNames.Add(member.Name)) + { + collidingMemberNames.Add(member.Name); + } + } + + QualifiedNamedTypeName? GetDeclaringTypeIfColliding(ISymbol symbol) => collidingMemberNames.Contains(symbol.Name) && !SymbolEqualityComparer.Default.Equals(symbol.ContainingType, formattedType) ? new(symbol.ContainingType) : null; + + if (this.options.Generator.Formatters.UsesMapMode || (contractAttr?.ConstructorArguments[0] is { Value: true })) + { + Dictionary claimedKeys = new(StringComparer.Ordinal); + void ReportNonUniqueNameIfApplicable(ISymbol item, string stringKey) + { + if (claimedKeys.TryGetValue(stringKey, out ISymbol newSymbol)) + { + SyntaxNode? syntax = newSymbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(this.cancellationToken); + var location = syntax switch + { + PropertyDeclarationSyntax { Identifier: SyntaxToken propertyId } => propertyId.GetLocation(), + VariableDeclaratorSyntax { Identifier: SyntaxToken fieldId } => fieldId.GetLocation(), + not null => syntax.GetLocation(), + _ => null, + }; + + reportDiagnostic?.Invoke(Diagnostic.Create(MsgPack00xMessagePackAnalyzer.CollidingMemberNamesInForceMapMode, location)); + } + + claimedKeys[stringKey] = item; + } + + // All public members are serialize target except [Ignore] member. + // Include private members if the type opted into that. + Accessibility minimumAccessibility = allowPrivateAttribute is true ? Accessibility.Private : Accessibility.Public; + isIntKey = false; + + var hiddenIntKey = 0; + + foreach (ISymbol baseItem in instanceMembers) + { + switch (baseItem) + { + case IPropertySymbol item: + { + ImmutableArray attributes = item.GetAttributes(); + if (attributes.Any(x => (x.AttributeClass.ApproximatelyEqual(this.typeReferences.IgnoreAttribute) || x.AttributeClass?.Name == this.typeReferences.IgnoreDataMemberAttribute?.Name)) || + item.DeclaredAccessibility < minimumAccessibility) + { + continue; + } + + bool isReadable = item.GetMethod is not null; + bool isWritable = item.SetMethod is not null; + if (!isReadable && !isWritable) + { + continue; + } + + bool isInitOnly = item.SetMethod?.IsInitOnly is true; + AttributeData? keyAttribute = attributes.FirstOrDefault(attributes => attributes.AttributeClass.ApproximatelyEqual(this.typeReferences.KeyAttribute)); + string stringKey = keyAttribute?.ConstructorArguments.Length == 1 && keyAttribute.ConstructorArguments[0].Value is string name ? name : item.Name; + ReportNonUniqueNameIfApplicable(item, stringKey); + + nestedFormatterRequired |= item.GetMethod is not null && IsPartialTypeRequired(item.GetMethod.DeclaredAccessibility); + nestedFormatterRequired |= item.SetMethod is not null && IsPartialTypeRequired(item.SetMethod.DeclaredAccessibility); + FormatterDescriptor? specialFormatter = GetSpecialFormatter(item); + MemberSerializationInfo member = new(true, isWritable, isReadable, isInitOnly, item.IsRequired, hiddenIntKey++, stringKey, item.Name, item.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), item.Type.ToDisplayString(BinaryWriteFormat), specialFormatter) + { + DeclaringType = GetDeclaringTypeIfColliding(item), + }; + if (!stringMembers.ContainsKey(member.StringKey)) + { + stringMembers.Add(member.StringKey, (member, item.Type)); + } + + if (specialFormatter is null) + { + this.CollectCore(item.Type, item); // recursive collect + } + } + + break; + case IFieldSymbol item: + { + ImmutableArray attributes = item.GetAttributes(); + if (attributes.Any(x => (x.AttributeClass.ApproximatelyEqual(this.typeReferences.IgnoreAttribute) || x.AttributeClass?.Name == this.typeReferences.IgnoreDataMemberAttribute?.Name)) || + item.DeclaredAccessibility < minimumAccessibility) + { + continue; + } + + AttributeData? keyAttribute = attributes.FirstOrDefault(attributes => attributes.AttributeClass.ApproximatelyEqual(this.typeReferences.KeyAttribute)); + string stringKey = keyAttribute?.ConstructorArguments.Length == 1 && keyAttribute.ConstructorArguments[0].Value is string name ? name : item.Name; + ReportNonUniqueNameIfApplicable(item, stringKey); + + nestedFormatterRequired |= IsPartialTypeRequired(item.DeclaredAccessibility); + FormatterDescriptor? specialFormatter = GetSpecialFormatter(item); + MemberSerializationInfo member = new(false, IsWritable: !item.IsReadOnly, IsReadable: true, IsInitOnly: false, item.IsRequired, hiddenIntKey++, stringKey, item.Name, item.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), item.Type.ToDisplayString(BinaryWriteFormat), specialFormatter) + { + DeclaringType = GetDeclaringTypeIfColliding(item), + }; + if (!stringMembers.ContainsKey(member.StringKey)) + { + stringMembers.Add(member.StringKey, (member, item.Type)); + } + + if (specialFormatter is null) + { + this.CollectCore(item.Type, item); // recursive collect + } + } + + break; + } + } + } + else + { + // Only KeyAttribute members + var searchFirst = true; + var hiddenIntKey = 0; + + foreach (IPropertySymbol item in instanceMembers.OfType()) + { + if (item.IsIndexer) + { + continue; // .tt files don't generate good code for this yet: https://github.com/neuecc/MessagePack-CSharp/issues/390 + } + + if (item.GetAttributes().Any(x => + { + var typeReferencesIgnoreDataMemberAttribute = this.typeReferences.IgnoreDataMemberAttribute; + return typeReferencesIgnoreDataMemberAttribute != null && (x.AttributeClass.ApproximatelyEqual(this.typeReferences.IgnoreAttribute) || x.AttributeClass.ApproximatelyEqual(typeReferencesIgnoreDataMemberAttribute)); + })) + { + nonPublicMembersAreSerialized |= (item.DeclaredAccessibility & Accessibility.Public) != Accessibility.Public; + continue; + } + + bool isReadable = item.GetMethod is not null; + bool isWritable = item.SetMethod is not null; + bool isInitOnly = item.SetMethod?.IsInitOnly is true; + if (!isReadable && !isWritable) + { + continue; + } + + FormatterDescriptor? specialFormatter = GetSpecialFormatter(item); + TypedConstant? key = item.GetAttributes().FirstOrDefault(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.KeyAttribute))?.ConstructorArguments[0]; + if (key is null) + { + if (contractAttr is not null) + { + // If the member is attributed with a type derived from KeyAttribute, that's incompatible with source generation + // since we cannot know what the key is using only C# syntax and the semantic model. + SyntaxReference? derivedKeyAttribute = item.GetAttributes().FirstOrDefault(att => att.AttributeClass.IsApproximatelyEqualOrDerivedFrom(this.typeReferences.KeyAttribute) == RoslynAnalyzerExtensions.EqualityMatch.LeftDerivesFromRight)?.ApplicationSyntaxReference; + + if (derivedKeyAttribute is null || suppressSourceGeneration is not true) + { + if (SymbolEqualityComparer.Default.Equals(item.ContainingType, formattedType)) + { + var syntax = item.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(); + var identifier = (syntax as PropertyDeclarationSyntax)?.Identifier ?? (syntax as ParameterSyntax)?.Identifier; + + Diagnostic diagnostic = derivedKeyAttribute is not null && suppressSourceGeneration is not true + ? Diagnostic.Create(MsgPack00xMessagePackAnalyzer.AOTDerivedKeyAttribute, derivedKeyAttribute.GetSyntax(this.cancellationToken).GetLocation()) + : Diagnostic.Create(MsgPack00xMessagePackAnalyzer.MemberNeedsKey, identifier?.GetLocation(), formattedType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), item.Name); + if (nonPublicMembersAreSerialized || (item.DeclaredAccessibility & Accessibility.Public) == Accessibility.Public) + { + this.reportDiagnostic?.Invoke(diagnostic); + } + else + { + deferredDiagnosticsForNonPublicMembers.Add(diagnostic); + } + } + else if (formattedType.BaseType is not null) + { + // The member was inherited, so we raise a special error at the location of the base type reference. + BaseTypeSyntax? baseSyntax = formattedType.DeclaringSyntaxReferences.SelectMany(sr => (IEnumerable?)((BaseTypeDeclarationSyntax)sr.GetSyntax()).BaseList?.Types ?? Array.Empty()) + .FirstOrDefault(bt => SymbolEqualityComparer.Default.Equals(this.compilation.GetSemanticModel(bt.SyntaxTree).GetTypeInfo(bt.Type).Type, item.ContainingType)); + if (baseSyntax is not null) + { + Diagnostic diagnostic = derivedKeyAttribute is not null && suppressSourceGeneration is not true + ? Diagnostic.Create(MsgPack00xMessagePackAnalyzer.AOTDerivedKeyAttribute, baseSyntax.GetLocation()) + : Diagnostic.Create( + MsgPack00xMessagePackAnalyzer.BaseTypeContainsUnattributedPublicMembers, + baseSyntax.GetLocation(), + item.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), + item.Name); + if (reportedDiagnostics.Add(diagnostic)) + { + if (nonPublicMembersAreSerialized || (item.DeclaredAccessibility & Accessibility.Public) == Accessibility.Public) + { + this.reportDiagnostic?.Invoke(diagnostic); + } + else + { + deferredDiagnosticsForNonPublicMembers.Add(diagnostic); + } + } + } + } + } + } + } + else + { + // If this attributed member is non-public, then all non-public members should be attributed. + nonPublicMembersAreSerialized |= (item.DeclaredAccessibility & Accessibility.Public) != Accessibility.Public; + + nestedFormatterRequired |= item.GetMethod is not null && IsPartialTypeRequired(item.GetMethod.DeclaredAccessibility); + if (item.SetMethod is not null && IsPartialTypeRequired(item.SetMethod.DeclaredAccessibility)) + { + nestedFormatterRequiredIfPropertyIsNotSetByDeserializingCtor.Add(item); + } + + var intKey = key is { Value: int intKeyValue } ? intKeyValue : default(int?); + var stringKey = key is { Value: string stringKeyValue } ? stringKeyValue : default; + if (intKey == null && stringKey == null) + { + this.reportDiagnostic?.Invoke(Diagnostic.Create(MsgPack00xMessagePackAnalyzer.BothStringAndIntKeyAreNull, ((PropertyDeclarationSyntax)item.DeclaringSyntaxReferences[0].GetSyntax()).Identifier.GetLocation(), formattedType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), item.Name)); + } + + // A property with an init accessor and an initializer has a default that will be discarded by the deserializer. + if (suppressSourceGeneration is not true && isInitOnly) + { + EqualsValueClauseSyntax? initializer = item.DeclaringSyntaxReferences.Select(s => (s.GetSyntax(this.cancellationToken) as PropertyDeclarationSyntax)?.Initializer).FirstOrDefault(i => i is not null); + if (initializer is not null) + { + this.reportDiagnostic?.Invoke(Diagnostic.Create(MsgPack00xMessagePackAnalyzer.AOTInitProperty, initializer.GetLocation())); + } + } + + if (searchFirst) + { + searchFirst = false; + isIntKey = intKey != null; + } + else + { + if ((isIntKey && intKey == null) || (!isIntKey && stringKey == null)) + { + this.reportDiagnostic?.Invoke(Diagnostic.Create(MsgPack00xMessagePackAnalyzer.DoNotMixStringAndIntKeys, item.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax().GetLocation())); + } + } + + if (isIntKey) + { + if (intMembers.ContainsKey(intKey!.Value)) + { + this.reportDiagnostic?.Invoke(Diagnostic.Create(MsgPack00xMessagePackAnalyzer.KeysMustBeUnique, item.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax().GetLocation())); + } + + var member = new MemberSerializationInfo(true, isWritable, isReadable, isInitOnly, item.IsRequired, intKey!.Value, item.Name, item.Name, item.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), item.Type.ToDisplayString(BinaryWriteFormat), specialFormatter) + { + DeclaringType = GetDeclaringTypeIfColliding(item), + }; + intMembers.Add(member.IntKey, (member, item.Type)); + } + else if (stringKey is not null) + { + if (stringMembers.ContainsKey(stringKey!)) + { + this.reportDiagnostic?.Invoke(Diagnostic.Create(MsgPack00xMessagePackAnalyzer.KeysMustBeUnique, item.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax().GetLocation())); + } + + var member = new MemberSerializationInfo(true, isWritable, isReadable, isInitOnly, item.IsRequired, hiddenIntKey++, stringKey!, item.Name, item.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), item.Type.ToDisplayString(BinaryWriteFormat), specialFormatter) + { + DeclaringType = GetDeclaringTypeIfColliding(item), + }; + stringMembers.Add(member.StringKey, (member, item.Type)); + } + + var messagePackFormatter = item.GetAttributes().FirstOrDefault(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.FormatterAttribute))?.ConstructorArguments[0]; + + if (messagePackFormatter == null) + { + // recursive collect + this.CollectCore(item.Type, item); + } + } + } + + foreach (IFieldSymbol item in formattedType.GetAllMembers().OfType()) + { + if (item.IsImplicitlyDeclared || item.IsStatic) + { + continue; + } + + if (item.GetAttributes().Any(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.IgnoreAttribute))) + { + nonPublicMembersAreSerialized |= (item.DeclaredAccessibility & Accessibility.Public) != Accessibility.Public; + continue; + } + + FormatterDescriptor? specialFormatter = GetSpecialFormatter(item); + TypedConstant? key = item.GetAttributes().FirstOrDefault(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.KeyAttribute))?.ConstructorArguments[0]; + if (key is null) + { + if (contractAttr is not null) + { + // If the member is attributed with a type derived from KeyAttribute, that's incompatible with source generation + // since we cannot know what the key is using only C# syntax and the semantic model. + SyntaxReference? derivedKeyAttribute = item.GetAttributes().FirstOrDefault(att => att.AttributeClass.IsApproximatelyEqualOrDerivedFrom(this.typeReferences.KeyAttribute) == RoslynAnalyzerExtensions.EqualityMatch.LeftDerivesFromRight)?.ApplicationSyntaxReference; + + if (derivedKeyAttribute is null || suppressSourceGeneration is not true) + { + if (SymbolEqualityComparer.Default.Equals(item.ContainingType, formattedType)) + { + Diagnostic diagnostic = derivedKeyAttribute is not null && suppressSourceGeneration is not true + ? Diagnostic.Create(MsgPack00xMessagePackAnalyzer.AOTDerivedKeyAttribute, derivedKeyAttribute.GetSyntax(this.cancellationToken).GetLocation()) + : Diagnostic.Create(MsgPack00xMessagePackAnalyzer.MemberNeedsKey, item.DeclaringSyntaxReferences[0].GetSyntax().GetLocation(), formattedType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), item.Name); + if (nonPublicMembersAreSerialized || (item.DeclaredAccessibility & Accessibility.Public) == Accessibility.Public) + { + this.reportDiagnostic?.Invoke(diagnostic); + } + else + { + deferredDiagnosticsForNonPublicMembers.Add(diagnostic); + } + } + else if (formattedType.BaseType is not null) + { + // The member was inherited, so we raise a special error at the location of the base type reference. + BaseTypeSyntax? baseSyntax = formattedType.DeclaringSyntaxReferences.SelectMany(sr => (IEnumerable?)((BaseTypeDeclarationSyntax)sr.GetSyntax()).BaseList?.Types ?? Array.Empty()) + .FirstOrDefault(bt => SymbolEqualityComparer.Default.Equals(this.compilation.GetSemanticModel(bt.SyntaxTree).GetTypeInfo(bt.Type).Type, item.ContainingType)); + if (baseSyntax is not null) + { + Diagnostic diagnostic = derivedKeyAttribute is not null && suppressSourceGeneration is not true + ? Diagnostic.Create(MsgPack00xMessagePackAnalyzer.AOTDerivedKeyAttribute, baseSyntax.GetLocation()) + : Diagnostic.Create( + MsgPack00xMessagePackAnalyzer.BaseTypeContainsUnattributedPublicMembers, + baseSyntax.GetLocation(), + item.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), + item.Name); + if (reportedDiagnostics.Add(diagnostic)) + { + if (nonPublicMembersAreSerialized || (item.DeclaredAccessibility & Accessibility.Public) == Accessibility.Public) + { + this.reportDiagnostic?.Invoke(diagnostic); + } + else + { + deferredDiagnosticsForNonPublicMembers.Add(diagnostic); + } + } + } + } + } + } + } + else + { + // If this attributed member is non-public, then all non-public members should be attributed. + nonPublicMembersAreSerialized |= (item.DeclaredAccessibility & Accessibility.Public) != Accessibility.Public; + + nestedFormatterRequired |= IsPartialTypeRequired(item.DeclaredAccessibility); + + var intKey = key is { Value: int intKeyValue } ? intKeyValue : default(int?); + var stringKey = key is { Value: string stringKeyValue } ? stringKeyValue : default; + if (intKey == null && stringKey == null) + { + this.reportDiagnostic?.Invoke(Diagnostic.Create(MsgPack00xMessagePackAnalyzer.BothStringAndIntKeyAreNull, item.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax().GetLocation())); + } + + if (searchFirst) + { + searchFirst = false; + isIntKey = intKey != null; + } + else + { + if ((isIntKey && intKey == null) || (!isIntKey && stringKey == null)) + { + this.reportDiagnostic?.Invoke(Diagnostic.Create(MsgPack00xMessagePackAnalyzer.DoNotMixStringAndIntKeys, item.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax().GetLocation())); + } + } + + if (isIntKey) + { + if (intMembers.ContainsKey(intKey!.Value)) + { + this.reportDiagnostic?.Invoke(Diagnostic.Create(MsgPack00xMessagePackAnalyzer.KeysMustBeUnique, item.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax().GetLocation())); + } + + var member = new MemberSerializationInfo(true, IsWritable: !item.IsReadOnly, IsReadable: true, IsInitOnly: false, item.IsRequired, intKey!.Value, item.Name, item.Name, item.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), item.Type.ToDisplayString(BinaryWriteFormat), specialFormatter) + { + DeclaringType = GetDeclaringTypeIfColliding(item), + }; + intMembers.Add(member.IntKey, (member, item.Type)); + } + else + { + if (stringMembers.ContainsKey(stringKey!)) + { + this.reportDiagnostic?.Invoke(Diagnostic.Create(MsgPack00xMessagePackAnalyzer.KeysMustBeUnique, item.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax().GetLocation())); + } + + var member = new MemberSerializationInfo(true, IsWritable: !item.IsReadOnly, IsReadable: true, IsInitOnly: false, item.IsRequired, hiddenIntKey++, stringKey!, item.Name, item.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), item.Type.ToDisplayString(BinaryWriteFormat), specialFormatter) + { + DeclaringType = GetDeclaringTypeIfColliding(item), + }; + stringMembers.Add(member.StringKey, (member, item.Type)); + } + + var messagePackFormatter = item.GetAttributes().FirstOrDefault(x => x.AttributeClass.ApproximatelyEqual(this.typeReferences.FormatterAttribute))?.ConstructorArguments[0]; + + if (messagePackFormatter == null) + { + // recursive collect + this.CollectCore(item.Type, item); + } + } + } + } + + // If we discovered midway through that we should be reporting diagnostics for non-public members, + // report the ones we missed along the way. + if ((nonPublicMembersAreSerialized || allowPrivateAttribute is true) && this.reportDiagnostic is not null) + { + foreach (Diagnostic deferred in deferredDiagnosticsForNonPublicMembers) + { + this.reportDiagnostic(deferred); + } + } + + // GetConstructor + var ctorEnumerator = default(IEnumerator); + var ctor = formattedType.Constructors.SingleOrDefault(x => x.GetAttributes().Any(y => y.AttributeClass != null && y.AttributeClass.ApproximatelyEqual(this.typeReferences.SerializationConstructorAttribute))); + if (ctor is null) + { + ctorEnumerator = formattedType.Constructors + .OrderByDescending(x => x.DeclaredAccessibility) + .ThenByDescending(x => x.Parameters.Length) + .GetEnumerator(); + + if (ctorEnumerator.MoveNext()) + { + ctor = ctorEnumerator.Current; + } + } + + // struct allows null ctor + if (ctor is null && isClass) + { + this.reportDiagnostic?.Invoke(Diagnostic.Create(MsgPack00xMessagePackAnalyzer.NoDeserializingConstructor, GetIdentifierLocation(formattedType))); + return null; + } + + var constructorParameters = new List(); + if (ctor is not null) + { + nestedFormatterRequired |= IsPartialTypeRequired(ctor.DeclaredAccessibility); + + var constructorLookupDictionary = stringMembers.ToLookup(x => x.Value.Info.Name, x => x, StringComparer.OrdinalIgnoreCase); + IReadOnlyDictionary ctorParamIndexIntMembersDictionary = intMembers + .OrderBy(x => x.Key).Select((x, i) => (Key: x.Value, Index: i)) + .ToDictionary(x => x.Index, x => x.Key); + do + { + constructorParameters.Clear(); + var ctorParamIndex = 0; + foreach (IParameterSymbol item in ctor!.Parameters) + { + if (isIntKey) + { + if (ctorParamIndexIntMembersDictionary.TryGetValue(ctorParamIndex, out (MemberSerializationInfo Info, ITypeSymbol TypeSymbol) member)) + { + if (this.compilation.ClassifyConversion(member.TypeSymbol, item.Type) is { IsImplicit: true } && member.Info.IsReadable) + { + constructorParameters.Add(member.Info); + } + else + { + if (ctorEnumerator != null) + { + ctor = null; + continue; + } + else + { + this.reportDiagnostic?.Invoke(Diagnostic.Create(MsgPack00xMessagePackAnalyzer.DeserializingConstructorParameterTypeMismatch, GetLocation(item))); + return null; + } + } + } + else + { + if (ctorEnumerator != null) + { + ctor = null; + continue; + } + else + { + this.reportDiagnostic?.Invoke(Diagnostic.Create(MsgPack00xMessagePackAnalyzer.DeserializingConstructorParameterIndexMissing, GetParameterListLocation(ctor))); + return null; + } + } + } + else + { + IEnumerable> hasKey = constructorLookupDictionary[item.Name]; + using var enumerator = hasKey.GetEnumerator(); + + // hasKey.Count() == 0 + if (!enumerator.MoveNext()) + { + if (ctorEnumerator == null) + { + this.reportDiagnostic?.Invoke(Diagnostic.Create(MsgPack00xMessagePackAnalyzer.DeserializingConstructorParameterNameMissing, GetParameterListLocation(ctor))); + return null; + } + + ctor = null; + continue; + } + + var first = enumerator.Current.Value; + + // hasKey.Count() != 1 + if (enumerator.MoveNext()) + { + if (ctorEnumerator == null) + { + this.reportDiagnostic?.Invoke(Diagnostic.Create(MsgPack00xMessagePackAnalyzer.DeserializingConstructorParameterNameDuplicate, GetLocation(item))); + return null; + } + + ctor = null; + continue; + } + + MemberSerializationInfo paramMember = first.Info; + if (item.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == paramMember.Type && paramMember.IsReadable) + { + constructorParameters.Add(paramMember); + } + else + { + if (ctorEnumerator == null) + { + this.reportDiagnostic?.Invoke(Diagnostic.Create(MsgPack00xMessagePackAnalyzer.DeserializingConstructorParameterTypeMismatch, GetLocation(item))); + return null; + } + + ctor = null; + continue; + } + } + + ctorParamIndex++; + } + } + while (TryGetNextConstructor(ctorEnumerator, ref ctor)); + + if (ctor == null) + { + this.reportDiagnostic?.Invoke(Diagnostic.Create(MsgPack00xMessagePackAnalyzer.NoDeserializingConstructor, GetIdentifierLocation(formattedType))); + } + } + + var hasSerializationConstructor = formattedType.AllInterfaces.Any(x => x.ApproximatelyEqual(this.typeReferences.IMessagePackSerializationCallbackReceiver)); + var needsCastOnBefore = true; + var needsCastOnAfter = true; + if (hasSerializationConstructor) + { + needsCastOnBefore = !formattedType.GetMembers("OnBeforeSerialize").Any(); + needsCastOnAfter = !formattedType.GetMembers("OnAfterDeserialize").Any(); + } + + if (contractAttr is null) + { + if (formattedType.IsDefinition) + { + Location location = callerSymbol is not null ? callerSymbol.Locations[0] : formattedType.Locations[0]; + var targetName = callerSymbol is not null ? callerSymbol.ContainingType.Name + "." + callerSymbol.Name : formattedType.Name; + + ImmutableDictionary typeInfo = ImmutableDictionary.Create().Add("type", formattedType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); + this.reportDiagnostic?.Invoke(Diagnostic.Create(MsgPack00xMessagePackAnalyzer.TypeMustBeMessagePackObject, location, typeInfo, targetName)); + } + + // Indicate to our caller that we don't have a valid object. + return null; + } + + if (allowPrivateAttribute is not true && (nonPublicMembersAreSerialized || ctor is { DeclaredAccessibility: not Accessibility.Public } || formattedType.GetEffectiveAccessibility() is not Accessibility.Public)) + { + if (contractAttr.ApplicationSyntaxReference?.GetSyntax(this.cancellationToken) is AttributeSyntax attSyntax) + { + Location location = attSyntax.GetLocation(); + + // If the user is explicitly setting AllowPrivate = false, set the location more precisely. + if (allowPrivateAttribute is false) + { + location = attSyntax.ArgumentList?.Arguments.First(a => a.NameEquals?.Name.Identifier.ValueText == Constants.AllowPrivatePropertyName)?.Expression.GetLocation() ?? location; + } + + this.reportDiagnostic?.Invoke(Diagnostic.Create(MsgPack00xMessagePackAnalyzer.MessagePackObjectAllowPrivateRequired, location)); + } + } + + // If any property had a private setter and does not appear in the deserializing constructor signature, + // we'll need a nested formatter. + foreach (IPropertySymbol property in nestedFormatterRequiredIfPropertyIsNotSetByDeserializingCtor) + { + nestedFormatterRequired |= !constructorParameters.Any(m => m.Name == property.Name); + } + + if (nestedFormatterRequired && nonPublicMembersAreSerialized) + { + // If the data type or any nesting types are not declared with partial, we cannot emit the formatter as a nested type within the data type + // as required in order to access the private members. + bool anyNonPartialTypesFound = false; + BaseTypeDeclarationSyntax[] nonPartialTypes = FindNonPartialTypes(formattedType).ToArray(); + if (nonPartialTypes.Length > 0) + { + BaseTypeDeclarationSyntax? targetType = formattedType.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() as BaseTypeDeclarationSyntax; + Location? primaryLocation = targetType?.Identifier.GetLocation(); + Location[]? addlLocations = nonPartialTypes.Where(t => t != targetType).Select(t => t.Identifier.GetLocation()).ToArray(); + this.reportDiagnostic?.Invoke(Diagnostic.Create(MsgPack00xMessagePackAnalyzer.PartialTypeRequired, primaryLocation, (IEnumerable?)addlLocations)); + anyNonPartialTypesFound = true; + } + + if (anyNonPartialTypesFound) + { + return null; + } + } + + INamedTypeSymbol? formattedTypeDefinition = formattedType.IsGenericType ? formattedType.ConstructUnboundGenericType() : null; + ObjectSerializationInfo info = ObjectSerializationInfo.Create( + formattedType, + isClass: isClass, + nestedFormatterRequired: nestedFormatterRequired, + genericTypeParameters: formattedType.IsGenericType ? formattedType.TypeParameters.Select(t => GenericTypeParameterInfo.Create(t)).ToArray() : Array.Empty(), + constructorParameters: constructorParameters.ToArray(), + isIntKey: isIntKey, + members: isIntKey ? intMembers.Values.Select(v => v.Info).ToArray() : stringMembers.Values.Select(v => v.Info).ToArray(), + hasIMessagePackSerializationCallbackReceiver: hasSerializationConstructor, + needsCastOnAfter: needsCastOnAfter, + needsCastOnBefore: needsCastOnBefore, + this.options.Generator.Resolver); + + return info; + } + + private static IEnumerable FindNonPartialTypes(ITypeSymbol target) + { + return from x in CodeAnalysisUtilities.EnumerateTypeAndContainingTypes(target) + where x.FirstDeclaration?.Modifiers.Any(SyntaxKind.PartialKeyword) is false + select x.FirstDeclaration; + } + + private static Location? GetIdentifierLocation(INamedTypeSymbol type) => ((BaseTypeDeclarationSyntax?)type.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax())?.Identifier.GetLocation(); + + private static Location? GetLocation(IParameterSymbol parameter) => parameter.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax().GetLocation(); + + private static Location? GetParameterListLocation(IMethodSymbol? method) => ((BaseMethodDeclarationSyntax?)method?.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax())?.ParameterList.GetLocation(); + + private static string GetGenericFormatterClassName(INamedTypeSymbol type) + { + return type.Name; + } + + private static string GetMinimallyQualifiedClassName(INamedTypeSymbol type) + { + var name = type.ContainingType is object ? GetMinimallyQualifiedClassName(type.ContainingType) + "_" : string.Empty; + name += type.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat); + name = name.Replace('.', '_'); + name = name.Replace('<', '_'); + name = name.Replace('>', '_'); + name = Regex.Replace(name, @"\[([,])*\]", match => $"Array{match.Length - 1}"); + name = name.Replace("?", string.Empty); + return name; + } + + private static bool TryGetNextConstructor(IEnumerator? ctorEnumerator, ref IMethodSymbol? ctor) + { + if (ctorEnumerator == null || ctor != null) + { + return false; + } + + if (ctorEnumerator.MoveNext()) + { + ctor = ctorEnumerator.Current; + return true; + } + else + { + ctor = null; + return false; + } + } + + private static bool IsPartialTypeRequired(Accessibility accessibility) => accessibility is not (Accessibility.Public or Accessibility.Internal or Accessibility.ProtectedOrInternal); + + private static bool IsAllowAccessibility(ITypeSymbol symbol) + { + do + { + if (symbol.DeclaredAccessibility is not (Accessibility.Public or Accessibility.Internal)) + { + return false; + } + + symbol = symbol.ContainingType; + } + while (symbol is not null); + + return true; + } + + private static bool IsOpenGenericTypeRecursively(INamedTypeSymbol type) + { + return type.IsGenericType && type.TypeArguments.Any(x => x is ITypeParameterSymbol || (x is INamedTypeSymbol symbol && IsOpenGenericTypeRecursively(symbol))); + } +} diff --git a/src/MessagePack.SourceGenerator/CodeAnalysis/TypeParameter.cs b/src/MessagePack.SourceGenerator/CodeAnalysis/TypeParameter.cs new file mode 100644 index 000000000..4d1397fd1 --- /dev/null +++ b/src/MessagePack.SourceGenerator/CodeAnalysis/TypeParameter.cs @@ -0,0 +1,37 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; + +namespace MessagePack.SourceGenerator.CodeAnalysis; + +/// +/// Wraps a generic type parameter that is used as an (unresolved) generic type argument. +/// +/// The identifier (e.g. T). +internal record TypeParameter(string Identifier) : QualifiedTypeName, IComparable +{ + public TypeParameter(ITypeParameterSymbol symbol) + : this(symbol.Name) + { + } + + public override TypeKind Kind + { + get => TypeKind.TypeParameter; + init + { + if (value != TypeKind.TypeParameter) + { + throw new NotSupportedException(); + } + } + } + + public override bool IsRecord => false; + + public int CompareTo(TypeParameter other) => this.Identifier.CompareTo(other.Identifier); + + public override string GetQualifiedName(Qualifiers qualifier = Qualifiers.GlobalNamespace, GenericParameterStyle genericStyle = GenericParameterStyle.Identifiers, bool includeNullableAnnotation = false) + => this.Identifier; +} diff --git a/src/MessagePack.SourceGenerator/CodeAnalysis/UnionSerializationInfo.cs b/src/MessagePack.SourceGenerator/CodeAnalysis/UnionSerializationInfo.cs new file mode 100644 index 000000000..420345a48 --- /dev/null +++ b/src/MessagePack.SourceGenerator/CodeAnalysis/UnionSerializationInfo.cs @@ -0,0 +1,41 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; + +namespace MessagePack.SourceGenerator.CodeAnalysis; + +public sealed record UnionSerializationInfo : ResolverRegisterInfo +{ + public required ImmutableArray SubTypes { get; init; } + + public static UnionSerializationInfo Create(INamedTypeSymbol dataType, ImmutableArray subTypes, ResolverOptions resolverOptions) + { + ResolverRegisterInfo basicInfo = Create(dataType, resolverOptions); + return new UnionSerializationInfo + { + DataType = basicInfo.DataType, + Formatter = basicInfo.Formatter, + SubTypes = subTypes, + }; + } + + public bool Equals(UnionSerializationInfo? other) + { + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return base.Equals(other) + && SubTypes.SequenceEqual(other.SubTypes); + } + + public override int GetHashCode() => base.GetHashCode(); +} diff --git a/src/MessagePack.SourceGenerator/CodeAnalysis/UnionSubTypeInfo.cs b/src/MessagePack.SourceGenerator/CodeAnalysis/UnionSubTypeInfo.cs new file mode 100644 index 000000000..88eaecaa1 --- /dev/null +++ b/src/MessagePack.SourceGenerator/CodeAnalysis/UnionSubTypeInfo.cs @@ -0,0 +1,6 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace MessagePack.SourceGenerator.CodeAnalysis; + +public record UnionSubTypeInfo(int Key, string Type); diff --git a/src/MessagePack.SourceGenerator/CompositeResolverGenerator.cs b/src/MessagePack.SourceGenerator/CompositeResolverGenerator.cs new file mode 100644 index 000000000..9a29d0496 --- /dev/null +++ b/src/MessagePack.SourceGenerator/CompositeResolverGenerator.cs @@ -0,0 +1,78 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using MessagePack.SourceGenerator.Transforms; +using Microsoft.CodeAnalysis; +using static MessagePack.SourceGenerator.Constants; + +namespace MessagePack.SourceGenerator; + +[Generator(LanguageNames.CSharp)] +public class CompositeResolverGenerator : IIncrementalGenerator +{ + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var attributeData = context.SyntaxProvider.ForAttributeWithMetadataName( + $"{AttributeNamespace}.{CompositeResolverAttributeName}", + predicate: static (node, ct) => true, + transform: static (context, ct) => ( + ResolverName: new QualifiedNamedTypeName((INamedTypeSymbol)context.TargetSymbol), + Attribute: context.Attributes.Single())); + + IncrementalValueProvider options = GeneratorUtilities.GetAnalyzerOption(context); + + var resolvers = attributeData.Combine(context.CompilationProvider).Select((leftRight, cancellationToken) => + { + var source = leftRight.Left; + var compilation = leftRight.Right; + + bool includeLocalFormatters = source.Attribute.NamedArguments.FirstOrDefault(kv => kv.Key == CompositeResolverAttributeIncludeLocalFormattersPropertyName).Value.Value is true; + + if (source.Attribute.ConstructorArguments.Length > 0 && source.Attribute.ConstructorArguments[0].Kind == TypedConstantKind.Array) + { + // Get the semantic model we'll use for accessibility checks. + // We'll be accessing these members from a new source file, so it doesn't matter + // which existing syntax tree's semantic model we use to test for accessibility. + SemanticModel semanticModel = compilation.GetSemanticModel(compilation.SyntaxTrees.First()); + string[] resolverCreationExpressions = AnalyzerUtilities.ResolverSymbolToInstanceExpression( + semanticModel, + source.Attribute.ConstructorArguments[0].Values.Select(tc => tc.Value as INamedTypeSymbol)).ToArray(); + string[] formatterCreationExpressions = AnalyzerUtilities.FormatterSymbolToInstanceExpression( + semanticModel, + source.Attribute.ConstructorArguments[0].Values.Select(tc => tc.Value as INamedTypeSymbol)).ToArray(); + + return (source.ResolverName, ResolverCreationExpressions: resolverCreationExpressions, FormatterCreationExpressions: formatterCreationExpressions, IncludeLocalFormatters: includeLocalFormatters); + } + else + { + return (source.ResolverName, ResolverCreationExpressions: Array.Empty(), FormatterCreationExpressions: Array.Empty(), IncludeLocalFormatters: includeLocalFormatters); + } + }); + + context.RegisterSourceOutput(resolvers.Combine(options), (context, source) => + { + AnalyzerOptions options = source.Right; + + string[] formatterCreationExpressions = source.Left.FormatterCreationExpressions; + string[] resolverCreationExpressions = source.Left.ResolverCreationExpressions; + if (source.Left.IncludeLocalFormatters) + { + HashSet allFormatters = new(formatterCreationExpressions, StringComparer.Ordinal); + allFormatters.UnionWith(options.KnownFormatters.Select(f => f.InstanceExpression)); + formatterCreationExpressions = allFormatters.ToArray(); + + HashSet allResolvers = new(resolverCreationExpressions, StringComparer.Ordinal); + allResolvers.Add($"{options.Generator.Resolver.Name.GetQualifiedName()}.Instance"); + resolverCreationExpressions = allResolvers.ToArray(); + } + + CompositeResolverTemplate generator = new() + { + ResolverName = source.Left.ResolverName, + ResolverInstanceExpressions = resolverCreationExpressions, + FormatterInstanceExpressions = formatterCreationExpressions, + }; + context.AddSource(generator.FileName, generator.TransformText()); + }); + } +} diff --git a/src/MessagePack.SourceGenerator/Directory.Build.props b/src/MessagePack.SourceGenerator/Directory.Build.props new file mode 100644 index 000000000..ea193bb7a --- /dev/null +++ b/src/MessagePack.SourceGenerator/Directory.Build.props @@ -0,0 +1,6 @@ + + + true + + + diff --git a/src/MessagePack.SourceGenerator/GeneratorUtilities.cs b/src/MessagePack.SourceGenerator/GeneratorUtilities.cs new file mode 100644 index 000000000..ca35f2341 --- /dev/null +++ b/src/MessagePack.SourceGenerator/GeneratorUtilities.cs @@ -0,0 +1,73 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using static MessagePack.SourceGenerator.Constants; + +namespace MessagePack.SourceGenerator; + +internal static class GeneratorUtilities +{ + internal static IncrementalValueProvider GetAnalyzerOption(IncrementalGeneratorInitializationContext context) + { + // Search for [assembly: MessagePackKnownFormatter(typeof(SomeFormatter))] + var customFormatters = context.SyntaxProvider.ForAttributeWithMetadataName( + $"{AttributeNamespace}.{MessagePackKnownFormatterAttributeName}", + predicate: static (node, ct) => true, + transform: static (context, ct) => AnalyzerUtilities.ParseKnownFormatterAttribute(context.Attributes, ct)).Collect(); + + // Search for [assembly: MessagePackAssumedFormattable(typeof(SomeCustomType))] + var customFormattedTypes = context.SyntaxProvider.ForAttributeWithMetadataName( + $"{AttributeNamespace}.{MessagePackAssumedFormattableAttributeName}", + predicate: static (node, ct) => true, + transform: static (context, ct) => AnalyzerUtilities.ParseAssumedFormattableAttribute(context.Attributes, ct)).SelectMany((a, ct) => a).Collect(); + + // Search for all implementations of IMessagePackFormatter in the compilation. + var customFormattersInThisCompilation = context.SyntaxProvider.CreateSyntaxProvider( + predicate: static (node, ct) => node is ClassDeclarationSyntax { BaseList.Types.Count: > 0 }, + transform: (ctxt, ct) => + { + return ctxt.SemanticModel.GetDeclaredSymbol(ctxt.Node, ct) is INamedTypeSymbol symbol + && FormatterDescriptor.TryCreate(symbol, out FormatterDescriptor? formatter) + && !formatter.ExcludeFromSourceGeneratedResolver + ? formatter + : null; + }).Collect(); + + // Search for a [GeneratedMessagePackResolver] attribute (presumably on a partial class). + var resolverOptions = context.SyntaxProvider.ForAttributeWithMetadataName( + $"{AttributeNamespace}.{GeneratedMessagePackResolverAttributeName}", + predicate: static (node, ct) => true, + transform: static (context, ct) => AnalyzerUtilities.ParseGeneratorAttribute(context.Attributes, context.TargetSymbol, ct)).Collect().Select((a, ct) => a.SingleOrDefault(ao => ao is not null)); + + // Assembly an aggregating AnalyzerOptions object from the attributes and interface implementations that we've found. + var options = resolverOptions.Combine(customFormattedTypes).Combine(customFormatters).Combine(customFormattersInThisCompilation) + .Select(static (input, ct) => + { + AnalyzerOptions? options = input.Left.Left.Left ?? new() { IsGeneratingSource = true }; + ImmutableArray formatterImplementations = input.Right; + + ImmutableArray formattableTypes = input.Left.Left.Right; + ImmutableHashSet formatterTypes = input.Left.Right.Aggregate( + ImmutableHashSet.Empty, + (first, second) => first.Union(second)); + + // Merge the formatters discovered through attributes (which need only reference formatters from other assemblies), + // with formatters discovered in the project being compiled. + formatterTypes = formatterImplementations.Aggregate( + formatterTypes, + (first, second) => second is not null ? first.Add(second) : first); + + options = options.WithFormatterTypes(formattableTypes, formatterTypes); + + return options; + }); + + return options; + } +} diff --git a/src/MessagePack.SourceGenerator/IGeneratorContext.cs b/src/MessagePack.SourceGenerator/IGeneratorContext.cs new file mode 100644 index 000000000..d3b6bf4ac --- /dev/null +++ b/src/MessagePack.SourceGenerator/IGeneratorContext.cs @@ -0,0 +1,15 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; + +namespace MessagePack.SourceGenerator; + +public interface IGeneratorContext +{ + CancellationToken CancellationToken { get; } + + void AddSource(string hintName, string source); + + void ReportDiagnostic(Diagnostic diagnostic); +} diff --git a/src/MessagePack.SourceGenerator/MessagePack.SourceGenerator.csproj b/src/MessagePack.SourceGenerator/MessagePack.SourceGenerator.csproj new file mode 100644 index 000000000..e3c4c5e90 --- /dev/null +++ b/src/MessagePack.SourceGenerator/MessagePack.SourceGenerator.csproj @@ -0,0 +1,103 @@ + + + + netstandard2.0 + enable + enable + True + cs + embedded + false + true + + + + + + + + + + + + + + + + + + True + True + CompositeResolverTemplate.tt + + + True + True + EnumTemplate.tt + + + True + True + FormatterTemplate.tt + + + True + True + ResolverTemplate.tt + + + True + True + StringKeyFormatterTemplate.tt + + + %(FileName).tt + True + True + + + True + True + UnionTemplate.tt + + + + + + TextTemplatingFilePreprocessor + CompositeResolverTemplate.cs + + + EnumTemplate.cs + TextTemplatingFilePreprocessor + + + FormatterTemplate.cs + TextTemplatingFilePreprocessor + + + ResolverTemplate.cs + TextTemplatingFilePreprocessor + + + StringKeyFormatterTemplate.cs + TextTemplatingFilePreprocessor + MessagePack.SourceGenerator.Transforms + + + UnionTemplate.cs + TextTemplatingFilePreprocessor + + + + + + + + + + + + + + diff --git a/src/MessagePack.SourceGenerator/MessagePack.SourceGenerator.targets b/src/MessagePack.SourceGenerator/MessagePack.SourceGenerator.targets new file mode 100644 index 000000000..810d10fe0 --- /dev/null +++ b/src/MessagePack.SourceGenerator/MessagePack.SourceGenerator.targets @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/src/MessagePack.SourceGenerator/MessagePackGenerator.Emit.cs b/src/MessagePack.SourceGenerator/MessagePackGenerator.Emit.cs new file mode 100644 index 000000000..8d38d4223 --- /dev/null +++ b/src/MessagePack.SourceGenerator/MessagePackGenerator.Emit.cs @@ -0,0 +1,84 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Text; +using MessagePack.SourceGenerator.Transforms; +using Microsoft.CodeAnalysis; + +namespace MessagePack.SourceGenerator; + +public partial class MessagePackGenerator +{ +#pragma warning disable RS1035 // Do not use APIs banned for analyzers + private static readonly string FileHeader = """ +// + +#pragma warning disable 618, 612, 414, 168, CS1591, SA1129, SA1309, SA1312, SA1403, SA1649 + +""".Replace(Environment.NewLine, "\r\n"); +#pragma warning restore RS1035 // Do not use APIs banned for analyzers + + /// + /// Generates the specialized resolver and formatters for the types that require serialization in a given compilation. + /// + /// Generator context. + /// The full messagepack object model. + private static void Generate(IGeneratorContext context, FullModel model) + { + AnalyzerOptions options = model.Options; + StringBuilder sb = new(); + + foreach (EnumSerializationInfo info in model.EnumInfos) + { + EnumTemplate transform = new(options, info); + AddTransform(transform.TransformText(), transform.FileName); + } + + foreach (UnionSerializationInfo info in model.UnionInfos) + { + UnionTemplate transform = new(options, info); + AddTransform(transform.TransformText(), transform.FileName); + } + + foreach (ObjectSerializationInfo info in model.ObjectInfos) + { + IFormatterTemplate transform = info.IsStringKey + ? new StringKeyFormatterTemplate(options, info) + : new FormatterTemplate(options, info); + AddTransform(transform.TransformText(), transform.FileName); + } + + void AddTransform(string transformOutput, string uniqueFileName) + { + sb.Clear(); + sb.Append(FileHeader); + sb.Append(transformOutput); + context.AddSource(CodeAnalysisUtilities.GetSanitizedFileName(uniqueFileName), sb.ToString()); + sb.Clear(); + } + } + + private static void GenerateResolver(IGeneratorContext context, FullModel model) + { + if (model.IsEmpty) + { + return; + } + + AnalyzerOptions options = model.Options; + StringBuilder sb = new(); + + ResolverRegisterInfo[] registerInfos = [ + .. model.ArrayFormatterInfos, + .. model.GenericInfos, + .. model.EnumInfos, + .. model.UnionInfos, + .. model.ObjectInfos, + .. model.CustomFormatterInfos.Where(fi => fi.FormattableDataType.IsFormatterInSameAssembly), + ]; + ResolverTemplate resolverTemplate = new(options, registerInfos); + sb.Append(FileHeader); + sb.Append(resolverTemplate.TransformText()); + context.AddSource(resolverTemplate.FileName, sb.ToString()); + } +} diff --git a/src/MessagePack.SourceGenerator/MessagePackGenerator.cs b/src/MessagePack.SourceGenerator/MessagePackGenerator.cs new file mode 100644 index 000000000..66db96c20 --- /dev/null +++ b/src/MessagePack.SourceGenerator/MessagePackGenerator.cs @@ -0,0 +1,147 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using static MessagePack.SourceGenerator.Constants; +using AnalyzerOptions = MessagePack.SourceGenerator.CodeAnalysis.AnalyzerOptions; + +namespace MessagePack.SourceGenerator; + +[Generator(LanguageNames.CSharp)] +public partial class MessagePackGenerator : IIncrementalGenerator +{ + public void Initialize(IncrementalGeneratorInitializationContext context) + { + IncrementalValueProvider options = GeneratorUtilities.GetAnalyzerOption(context); + + var messagePackObjectTypes = context.SyntaxProvider.ForAttributeWithMetadataName( + $"{AttributeNamespace}.{MessagePackObjectAttributeName}", + predicate: static (node, _) => node is TypeDeclarationSyntax, + transform: static (context, _) => (ITypeSymbol)context.TargetSymbol); + + var unionTypes = context.SyntaxProvider.ForAttributeWithMetadataName( + $"{AttributeNamespace}.{MessagePackUnionAttributeName}", + predicate: static (node, _) => node is InterfaceDeclarationSyntax, + transform: static (context, _) => (ITypeSymbol)context.TargetSymbol); + + var combined = + messagePackObjectTypes.Collect().Combine(unionTypes.Collect()); + + var source = combined + .Combine(context.CompilationProvider) + .Combine(options) + .Select(static (s, ct) => + { + AnalyzerOptions options = s.Right; + + if (!ReferenceSymbols.TryCreate(s.Left.Right, out ReferenceSymbols? referenceSymbols) || referenceSymbols.MessagePackFormatter is null) + { + return default; + } + + List modelPerType = new(); + void Collect(ITypeSymbol typeSymbol) + { + if (TypeCollector.Collect(s.Left.Right, options, referenceSymbols, reportAnalyzerDiagnostic: null, typeSymbol, ct) is FullModel model) + { + modelPerType.Add(model); + } + } + + foreach (var typeSymbol in s.Left.Left.Left) + { + Collect(typeSymbol); + } + + foreach (var typeSymbol in s.Left.Left.Right) + { + Collect(typeSymbol); + } + + if (!options.KnownFormatters.IsEmpty) + { + var customFormatterInfos = FullModel.Empty.CustomFormatterInfos.Union( + from known in options.KnownFormatters + where known.InaccessibleDescriptor is null + from formatted in known.FormattableTypes + where !options.GetCollidingFormatterDataTypes(known.Name).Contains(formatted) // skip formatters with colliding types to avoid non-deterministic code generation + select new CustomFormatterRegisterInfo + { + Formatter = known.Name, + DataType = formatted.Name, + FormattableDataType = formatted, + CustomFormatter = known, + }); + modelPerType.Add(FullModel.Empty with { CustomFormatterInfos = customFormatterInfos, Options = options }); + } + + return FullModel.Combine(modelPerType.ToImmutableArray()); + }); + + var splittedSources = source + .SelectMany(static (s, ct) => + { + if (s is null) + { + return ImmutableArray.Empty; + } + + var models = new List(); + models.AddRange(s.ArrayFormatterInfos.Select(i => FullModel.Empty with { Options = s.Options, ArrayFormatterInfos = ImmutableSortedSet.Create(i) })); + models.AddRange(s.ObjectInfos.Select(i => FullModel.Empty with { Options = s.Options, ObjectInfos = ImmutableSortedSet.Create(i) })); + models.AddRange(s.EnumInfos.Select(i => FullModel.Empty with { Options = s.Options, EnumInfos = ImmutableSortedSet.Create(i) })); + models.AddRange(s.UnionInfos.Select(i => FullModel.Empty with { Options = s.Options, UnionInfos = ImmutableSortedSet.Create(i) })); + + return models.ToImmutableArray(); + }); + + context.RegisterSourceOutput(source, static (context, source) => + { + if (source is { Options.IsGeneratingSource: true }) + { + GenerateResolver(new GeneratorContext(context), source); + } + }); + + context.RegisterSourceOutput(splittedSources, static (context, source) => + { + if (source is { Options.IsGeneratingSource: true }) + { + Generate(new GeneratorContext(context), source); + } + }); + } + + private class Comparer : IEqualityComparer<(TypeDeclarationSyntax, Compilation)> + { + public static readonly Comparer Instance = new Comparer(); + + public bool Equals((TypeDeclarationSyntax, Compilation) x, (TypeDeclarationSyntax, Compilation) y) + { + return x.Item1.Equals(y.Item1); + } + + public int GetHashCode((TypeDeclarationSyntax, Compilation) obj) + { + return obj.Item1.GetHashCode(); + } + } + + private class GeneratorContext : IGeneratorContext + { + private SourceProductionContext context; + + public GeneratorContext(SourceProductionContext context) + { + this.context = context; + } + + public CancellationToken CancellationToken => context.CancellationToken; + + public void AddSource(string hintName, string source) => context.AddSource(hintName, source); + + public void ReportDiagnostic(Diagnostic diagnostic) => context.ReportDiagnostic(diagnostic); + } +} diff --git a/src/MessagePack.SourceGenerator/Shims/System.CodeDom.cs b/src/MessagePack.SourceGenerator/Shims/System.CodeDom.cs new file mode 100644 index 000000000..07618cc42 --- /dev/null +++ b/src/MessagePack.SourceGenerator/Shims/System.CodeDom.cs @@ -0,0 +1,143 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +#pragma warning disable SA1633 +#pragma warning disable SA1649 +#pragma warning disable SA1402 +#pragma warning disable SA1128 +#pragma warning disable SA1502 +#pragma warning disable SA1513 +#nullable disable + +using System.Collections; +using System.Globalization; + +namespace System.CodeDom.Compiler +{ + public class CompilerError + { + public CompilerError() : this(string.Empty, 0, 0, string.Empty, string.Empty) { } + + public CompilerError(string fileName, int line, int column, string errorNumber, string errorText) + { + Line = line; + Column = column; + ErrorNumber = errorNumber; + ErrorText = errorText; + FileName = fileName; + } + + public int Line { get; set; } + + public int Column { get; set; } + + public string ErrorNumber { get; set; } + + public string ErrorText { get; set; } + + public bool IsWarning { get; set; } + + public string FileName { get; set; } + + public override string ToString() => FileName.Length > 0 ? + string.Format(CultureInfo.InvariantCulture, "{0}({1},{2}) : {3} {4}: {5}", FileName, Line, Column, WarningString, ErrorNumber, ErrorText) : + string.Format(CultureInfo.InvariantCulture, "{0} {1}: {2}", WarningString, ErrorNumber, ErrorText); + + private string WarningString => IsWarning ? "warning" : "error"; + } + + public class CompilerErrorCollection : CollectionBase + { + public CompilerErrorCollection() { } + + public CompilerErrorCollection(CompilerErrorCollection value) + { + AddRange(value); + } + + public CompilerErrorCollection(CompilerError[] value) + { + AddRange(value); + } + + public CompilerError this[int index] + { + get => (CompilerError)List[index]; + set => List[index] = value; + } + + public int Add(CompilerError value) => List.Add(value); + + public void AddRange(CompilerError[] value) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + for (int i = 0; i < value.Length; i++) + { + Add(value[i]); + } + } + + public void AddRange(CompilerErrorCollection value) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + int currentCount = value.Count; + for (int i = 0; i < currentCount; i++) + { + Add(value[i]); + } + } + + public bool Contains(CompilerError value) => List.Contains(value); + + public void CopyTo(CompilerError[] array, int index) => List.CopyTo(array, index); + + public bool HasErrors + { + get + { + if (Count > 0) + { + foreach (CompilerError e in this) + { + if (!e.IsWarning) + { + return true; + } + } + } + return false; + } + } + + public bool HasWarnings + { + get + { + if (Count > 0) + { + foreach (CompilerError e in this) + { + if (e.IsWarning) + { + return true; + } + } + } + return false; + } + } + + public int IndexOf(CompilerError value) => List.IndexOf(value); + + public void Insert(int index, CompilerError value) => List.Insert(index, value); + + public void Remove(CompilerError value) => List.Remove(value); + } +} diff --git a/src/MessagePackAnalyzer/Strings.resx b/src/MessagePack.SourceGenerator/Strings.resx similarity index 100% rename from src/MessagePackAnalyzer/Strings.resx rename to src/MessagePack.SourceGenerator/Strings.resx diff --git a/src/MessagePack.SourceGenerator/ThisAssembly.cs b/src/MessagePack.SourceGenerator/ThisAssembly.cs new file mode 100644 index 000000000..f0ebece07 --- /dev/null +++ b/src/MessagePack.SourceGenerator/ThisAssembly.cs @@ -0,0 +1,26 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace MessagePack.SourceGenerator +{ + public static class ThisAssembly + { + public static string AssemblyFileVersion + { + get + { + var version = typeof(ThisAssembly).Assembly.GetName().Version.ToString(); + return version; + } + } + + public static Version Version + { + get + { + var version = typeof(ThisAssembly).Assembly.GetName().Version; + return version; + } + } + } +} diff --git a/src/MessagePack.SourceGenerator/Transforms/.editorconfig b/src/MessagePack.SourceGenerator/Transforms/.editorconfig new file mode 100644 index 000000000..1976b20e5 --- /dev/null +++ b/src/MessagePack.SourceGenerator/Transforms/.editorconfig @@ -0,0 +1,7 @@ +[*.tt] +indent_size = 4 +indent_style = tab + +[*.cs] +# RS1035: Do not use APIs banned for analyzers(such as Environment.NewLine) +dotnet_diagnostic.RS1035.severity = suggestion diff --git a/src/MessagePack.SourceGenerator/Transforms/.gitattributes b/src/MessagePack.SourceGenerator/Transforms/.gitattributes new file mode 100644 index 000000000..f707ed105 --- /dev/null +++ b/src/MessagePack.SourceGenerator/Transforms/.gitattributes @@ -0,0 +1,6 @@ +# Always check out .tt files with CRLF line endings so the code-behind file, which captures these, remains consistent with the .tt file. +# LF line endings don't trigger T4's automatic empty line removal, so CRLF is preferable. +*.tt eol=crlf + +# Even the T4-generated transform source can include line endings within C# strings, so we need to ensure that the .cs files are checked out with CRLF line endings too. +*.cs eol=crlf diff --git a/src/MessagePack.SourceGenerator/Transforms/CompositeResolverTemplate.cs b/src/MessagePack.SourceGenerator/Transforms/CompositeResolverTemplate.cs new file mode 100644 index 000000000..23ef5d1b3 --- /dev/null +++ b/src/MessagePack.SourceGenerator/Transforms/CompositeResolverTemplate.cs @@ -0,0 +1,361 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 17.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace MessagePack.SourceGenerator.Transforms +{ + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] + public partial class CompositeResolverTemplate : CompositeResolverTemplateBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("\r\nusing MsgPack = global::MessagePack;\r\n\r\n"); + using (TransformUtilities.EmitNestingTypesAndNamespaces(this.ResolverName, this.Write)) { + this.Write("\r\npartial class "); + this.Write(this.ToStringHelper.ToStringWithCulture(ResolverName.Name)); + this.Write(" : MsgPack::IFormatterResolver\r\n{\r\n\tpublic static readonly "); + this.Write(this.ToStringHelper.ToStringWithCulture(ResolverName.Name)); + this.Write(" Instance = new "); + this.Write(this.ToStringHelper.ToStringWithCulture(ResolverName.Name)); + this.Write("();\r\n\r\n\tprivate static readonly MsgPack::Formatters.IMessagePackFormatter[] forma" + + "tterList = new MsgPack::Formatters.IMessagePackFormatter[]\r\n\t{\r\n"); + foreach (string expr in FormatterInstanceExpressions) { + this.Write("\t\t"); + this.Write(this.ToStringHelper.ToStringWithCulture(expr)); + this.Write(",\r\n"); + } + this.Write("\t};\r\n\r\n\tprivate static readonly MsgPack::IFormatterResolver[] resolverList = new " + + "MsgPack::IFormatterResolver[]\r\n\t{\r\n"); + foreach (string expr in ResolverInstanceExpressions) { + this.Write("\t\t"); + this.Write(this.ToStringHelper.ToStringWithCulture(expr)); + this.Write(",\r\n"); + } + this.Write("\t};\r\n\r\n\tprivate "); + this.Write(this.ToStringHelper.ToStringWithCulture(ResolverName.Name)); + this.Write(@"() { } + + public MsgPack::Formatters.IMessagePackFormatter GetFormatter() + { + return FormatterCache.Formatter; + } + + static class FormatterCache + { + internal static readonly MsgPack::Formatters.IMessagePackFormatter Formatter; + + static FormatterCache() + { + foreach (var formatter in formatterList) + { + if (formatter is MsgPack::Formatters.IMessagePackFormatter f) + { + Formatter = f; + return; + } + } + + foreach (var resolver in resolverList) + { + var f = resolver.GetFormatter(); + if (f != null) + { + Formatter = f; + return; + } + } + } + } +} + +"); + } + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] + public class CompositeResolverTemplateBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/src/MessagePack.SourceGenerator/Transforms/CompositeResolverTemplate.tt b/src/MessagePack.SourceGenerator/Transforms/CompositeResolverTemplate.tt new file mode 100644 index 000000000..6e44be112 --- /dev/null +++ b/src/MessagePack.SourceGenerator/Transforms/CompositeResolverTemplate.tt @@ -0,0 +1,60 @@ +<#@ template debug="false" hostspecific="false" linePragmas="false" language="C#" #> + +using MsgPack = global::MessagePack; + +<# using (TransformUtilities.EmitNestingTypesAndNamespaces(this.ResolverName, this.Write)) { #> + +partial class <#= ResolverName.Name #> : MsgPack::IFormatterResolver +{ + public static readonly <#= ResolverName.Name #> Instance = new <#= ResolverName.Name #>(); + + private static readonly MsgPack::Formatters.IMessagePackFormatter[] formatterList = new MsgPack::Formatters.IMessagePackFormatter[] + { +<# foreach (string expr in FormatterInstanceExpressions) { #> + <#= expr #>, +<# } #> + }; + + private static readonly MsgPack::IFormatterResolver[] resolverList = new MsgPack::IFormatterResolver[] + { +<# foreach (string expr in ResolverInstanceExpressions) { #> + <#= expr #>, +<# } #> + }; + + private <#= ResolverName.Name #>() { } + + public MsgPack::Formatters.IMessagePackFormatter GetFormatter() + { + return FormatterCache.Formatter; + } + + static class FormatterCache + { + internal static readonly MsgPack::Formatters.IMessagePackFormatter Formatter; + + static FormatterCache() + { + foreach (var formatter in formatterList) + { + if (formatter is MsgPack::Formatters.IMessagePackFormatter f) + { + Formatter = f; + return; + } + } + + foreach (var resolver in resolverList) + { + var f = resolver.GetFormatter(); + if (f != null) + { + Formatter = f; + return; + } + } + } + } +} + +<# } #> diff --git a/src/MessagePack.GeneratorCore/Generator/EnumTemplate.cs b/src/MessagePack.SourceGenerator/Transforms/EnumTemplate.cs similarity index 81% rename from src/MessagePack.GeneratorCore/Generator/EnumTemplate.cs rename to src/MessagePack.SourceGenerator/Transforms/EnumTemplate.cs index 382112a3b..dd57a94ce 100644 --- a/src/MessagePack.GeneratorCore/Generator/EnumTemplate.cs +++ b/src/MessagePack.SourceGenerator/Transforms/EnumTemplate.cs @@ -7,11 +7,8 @@ // the code is regenerated. // // ------------------------------------------------------------------------------ -namespace MessagePackCompiler.Generator +namespace MessagePack.SourceGenerator.Transforms { - using System.Linq; - using System.Text; - using System.Collections.Generic; using System; /// @@ -25,52 +22,25 @@ public partial class EnumTemplate : EnumTemplateBase /// public virtual string TransformText() { - this.Write(@"// -// THIS (.cs) FILE IS GENERATED BY MPC(MessagePack-CSharp). DO NOT CHANGE IT. -// - -#pragma warning disable 618 -#pragma warning disable 612 -#pragma warning disable 414 -#pragma warning disable 168 -#pragma warning disable CS1591 // document public APIs - -#pragma warning disable SA1403 // File may only contain a single namespace -#pragma warning disable SA1649 // File name should match first type name - -namespace "); - this.Write(this.ToStringHelper.ToStringWithCulture(Namespace)); - this.Write("\r\n{\r\n"); - foreach(var info in EnumSerializationInfos) { - this.Write("\r\n public sealed class "); - this.Write(this.ToStringHelper.ToStringWithCulture(info.Name)); - this.Write("Formatter : global::MessagePack.Formatters.IMessagePackFormatter<"); - this.Write(this.ToStringHelper.ToStringWithCulture(info.FullName)); - this.Write(">\r\n {\r\n public void Serialize(ref global::MessagePack.MessagePackWriter" + - " writer, "); - this.Write(this.ToStringHelper.ToStringWithCulture(info.FullName)); - this.Write(" value, global::MessagePack.MessagePackSerializerOptions options)\r\n {\r\n " + - " writer.Write((global::System."); - this.Write(this.ToStringHelper.ToStringWithCulture(info.UnderlyingType)); - this.Write(")value);\r\n }\r\n\r\n public "); - this.Write(this.ToStringHelper.ToStringWithCulture(info.FullName)); - this.Write(" Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePac" + - "k.MessagePackSerializerOptions options)\r\n {\r\n return ("); - this.Write(this.ToStringHelper.ToStringWithCulture(info.FullName)); + this.Write("\r\nusing MsgPack = global::MessagePack;\r\n\r\n"); + using (this.EmitNestingTypesAndNamespaces(this.Write)) { + this.Write("\r\n\tinternal sealed class "); + this.Write(this.ToStringHelper.ToStringWithCulture(Info.Formatter.Name)); + this.Write(" : MsgPack::Formatters.IMessagePackFormatter<"); + this.Write(this.ToStringHelper.ToStringWithCulture(Info.DataType.GetQualifiedName())); + this.Write(">\r\n\t{\r\n\t\tpublic void Serialize(ref MsgPack::MessagePackWriter writer, "); + this.Write(this.ToStringHelper.ToStringWithCulture(Info.DataType.GetQualifiedName())); + this.Write(" value, MsgPack::MessagePackSerializerOptions options)\r\n\t\t{\r\n\t\t\twriter.Write(("); + this.Write(this.ToStringHelper.ToStringWithCulture(Info.UnderlyingTypeKeyword)); + this.Write(")value);\r\n\t\t}\r\n\r\n\t\tpublic "); + this.Write(this.ToStringHelper.ToStringWithCulture(Info.DataType.GetQualifiedName())); + this.Write(" Deserialize(ref MsgPack::MessagePackReader reader, MsgPack::MessagePackSerialize" + + "rOptions options)\r\n\t\t{\r\n\t\t\treturn ("); + this.Write(this.ToStringHelper.ToStringWithCulture(Info.DataType.GetQualifiedName())); this.Write(")reader.Read"); - this.Write(this.ToStringHelper.ToStringWithCulture(info.UnderlyingType)); - this.Write("();\r\n }\r\n }\r\n"); + this.Write(this.ToStringHelper.ToStringWithCulture(Info.UnderlyingTypeName)); + this.Write("();\r\n\t\t}\r\n\t}\r\n\r\n"); } - this.Write(@"} - -#pragma warning restore 168 -#pragma warning restore 414 -#pragma warning restore 618 -#pragma warning restore 612 - -#pragma warning restore SA1403 // File may only contain a single namespace -#pragma warning restore SA1649 // File name should match first type name -"); return this.GenerationEnvironment.ToString(); } } @@ -93,7 +63,7 @@ public class EnumTemplateBase /// /// The string builder that generation-time code is using to assemble generated output /// - protected System.Text.StringBuilder GenerationEnvironment + public System.Text.StringBuilder GenerationEnvironment { get { diff --git a/src/MessagePack.SourceGenerator/Transforms/EnumTemplate.tt b/src/MessagePack.SourceGenerator/Transforms/EnumTemplate.tt new file mode 100644 index 000000000..11ebf0779 --- /dev/null +++ b/src/MessagePack.SourceGenerator/Transforms/EnumTemplate.tt @@ -0,0 +1,21 @@ +<#@ template debug="false" hostspecific="false" linePragmas="false" language="C#" #> +<#@ assembly name="System.Core" #> + +using MsgPack = global::MessagePack; + +<# using (this.EmitNestingTypesAndNamespaces(this.Write)) { #> + + internal sealed class <#= Info.Formatter.Name #> : MsgPack::Formatters.IMessagePackFormatter<<#= Info.DataType.GetQualifiedName() #>> + { + public void Serialize(ref MsgPack::MessagePackWriter writer, <#= Info.DataType.GetQualifiedName() #> value, MsgPack::MessagePackSerializerOptions options) + { + writer.Write((<#= Info.UnderlyingTypeKeyword #>)value); + } + + public <#= Info.DataType.GetQualifiedName() #> Deserialize(ref MsgPack::MessagePackReader reader, MsgPack::MessagePackSerializerOptions options) + { + return (<#= Info.DataType.GetQualifiedName() #>)reader.Read<#= Info.UnderlyingTypeName #>(); + } + } + +<# } #> diff --git a/src/MessagePack.SourceGenerator/Transforms/FormatterTemplate.cs b/src/MessagePack.SourceGenerator/Transforms/FormatterTemplate.cs new file mode 100644 index 000000000..05b30737f --- /dev/null +++ b/src/MessagePack.SourceGenerator/Transforms/FormatterTemplate.cs @@ -0,0 +1,453 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 17.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace MessagePack.SourceGenerator.Transforms +{ + using System.Linq; + using System.Text; + using System.Collections.Generic; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] + public partial class FormatterTemplate : FormatterTemplateBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("\r\n#pragma warning disable CS8669 // We may leak nullable annotations into generat" + + "ed code.\r\n\r\nusing MsgPack = global::MessagePack;\r\n\r\n"); + using (this.EmitNestingTypesAndNamespaces(this.Write)) { + this.Write("\r\n"); + bool isFormatterResolverNecessary = GeneratorUtilities.ShouldUseFormatterResolver(Info.Members); + this.Write("\tinternal sealed class "); + this.Write(this.ToStringHelper.ToStringWithCulture(Info.Formatter.GetQualifiedName(Qualifiers.None))); + this.Write(" : MsgPack::Formatters.IMessagePackFormatter<"); + this.Write(this.ToStringHelper.ToStringWithCulture(Info.DataType.GetQualifiedName(genericStyle: GenericParameterStyle.Identifiers))); + this.Write(">\r\n"); + TransformUtilities.EmitTypeConstraints(Info.Formatter, this.Write); + this.Write("\t{\r\n"); + foreach (var item in Info.Members) { + if (item.CustomFormatter != null) { + this.Write("\t\tprivate readonly "); + this.Write(this.ToStringHelper.ToStringWithCulture(item.CustomFormatter.InstanceTypeName.GetQualifiedName(genericStyle: GenericParameterStyle.Arguments))); + this.Write(" __"); + this.Write(this.ToStringHelper.ToStringWithCulture(item.Name)); + this.Write("CustomFormatter__ = "); + this.Write(this.ToStringHelper.ToStringWithCulture(item.CustomFormatter.InstanceExpression)); + this.Write(";\r\n"); + } + } + this.Write("\r\n\t\tpublic void Serialize(ref MsgPack::MessagePackWriter writer, "); + this.Write(this.ToStringHelper.ToStringWithCulture(Info.DataType.GetQualifiedName(genericStyle: GenericParameterStyle.Identifiers))); + this.Write(" value, MsgPack::MessagePackSerializerOptions options)\r\n\t\t{\r\n"); + if (Info.IsClass) { + this.Write("\t\t\tif (value == null)\r\n\t\t\t{\r\n\t\t\t\twriter.WriteNil();\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n"); + } + + if (isFormatterResolverNecessary) { + this.Write("\t\t\tMsgPack::IFormatterResolver formatterResolver = options.Resolver;\r\n"); + } + + if (Info.HasIMessagePackSerializationCallbackReceiver) { + if (Info.NeedsCastOnBefore) { + this.Write("\t\t\t((MsgPack::IMessagePackSerializationCallbackReceiver)value).OnBeforeSerialize(" + + ");\r\n"); + } else { + this.Write("\t\t\tvalue.OnBeforeSerialize();\r\n"); + } + } + this.Write("\t\t\twriter.WriteArrayHeader("); + this.Write(this.ToStringHelper.ToStringWithCulture(Info.MaxKey + 1)); + this.Write(");\r\n"); + for (var i = 0; i <= Info.MaxKey; i++) { + var member = Info.GetMember(i); + if (member == null) { + this.Write("\t\t\twriter.WriteNil();\r\n"); + } else { + this.Write("\t\t\t"); + this.Write(this.ToStringHelper.ToStringWithCulture(member.GetSerializeMethodString())); + this.Write(";\r\n"); + } + } + this.Write("\t\t}\r\n\r\n\t\tpublic "); + this.Write(this.ToStringHelper.ToStringWithCulture(Info.DataType.GetQualifiedName(genericStyle: GenericParameterStyle.Identifiers))); + this.Write(" Deserialize(ref MsgPack::MessagePackReader reader, MsgPack::MessagePackSerialize" + + "rOptions options)\r\n\t\t{\r\n\t\t\tif (reader.TryReadNil())\r\n\t\t\t{\r\n"); + if (Info.IsClass) { + this.Write("\t\t\t\treturn null;\r\n"); + } else { + this.Write("\t\t\t\tthrow new global::System.InvalidOperationException(\"typecode is null, struct " + + "not supported\");\r\n"); + } + this.Write("\t\t\t}\r\n\r\n"); + if (Info.MaxKey == -1 && !Info.HasIMessagePackSerializationCallbackReceiver) { + this.Write("\t\t\treader.Skip();\r\n\t\t\treturn new "); + this.Write(this.ToStringHelper.ToStringWithCulture(Info.GetConstructorString())); + this.Write(";\r\n"); + } else { + this.Write("\t\t\toptions.Security.DepthStep(ref reader);\r\n"); + if (isFormatterResolverNecessary) { + this.Write("\t\t\tMsgPack::IFormatterResolver formatterResolver = options.Resolver;\r\n"); + } + this.Write("\t\t\tvar length = reader.ReadArrayHeader();\r\n"); + if (Info.MustDeserializeFieldsFirst) { + foreach (var member in Info.Members) { + this.Write("\t\t\tvar "); + this.Write(this.ToStringHelper.ToStringWithCulture(member.LocalVariableName)); + this.Write(" = default("); + this.Write(this.ToStringHelper.ToStringWithCulture(member.Type)); + this.Write(");\r\n"); + } +} else { + this.Write("\t\t\tvar ____result = new "); + this.Write(this.ToStringHelper.ToStringWithCulture(Info.GetConstructorString())); + this.Write(";\r\n"); + } + this.Write("\r\n\t\t\tfor (int i = 0; i < length; i++)\r\n\t\t\t{\r\n\t\t\t\tswitch (i)\r\n\t\t\t\t{\r\n"); + for (var memberIndex = 0; memberIndex <= Info.MaxKey; memberIndex++) { + var member = Info.GetMember(memberIndex); + if (member == null) { continue; } + this.Write("\t\t\t\t\tcase "); + this.Write(this.ToStringHelper.ToStringWithCulture(member.IntKey)); + this.Write(":\r\n"); + if (Info.MustDeserializeFieldsFirst) { + this.Write("\t\t\t\t\t\t"); + this.Write(this.ToStringHelper.ToStringWithCulture(member.LocalVariableName)); + this.Write(" = "); + this.Write(this.ToStringHelper.ToStringWithCulture(member.GetDeserializeMethodString())); + this.Write(";\r\n"); + } else { + if (member.IsWritable) { + this.Write("\t\t\t\t\t\t"); + this.Write(this.ToStringHelper.ToStringWithCulture(member.GetMemberAccess("____result"))); + this.Write(" = "); + this.Write(this.ToStringHelper.ToStringWithCulture(member.GetDeserializeMethodString())); + this.Write(";\r\n"); + } else { + this.Write("\t\t\t\t\t\treader.Skip();\r\n"); + } + } + this.Write("\t\t\t\t\t\tbreak;\r\n"); + } + this.Write("\t\t\t\t\tdefault:\r\n\t\t\t\t\t\treader.Skip();\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n"); + if (Info.MustDeserializeFieldsFirst) { + this.Write("\t\t\tvar ____result = new "); + this.Write(this.ToStringHelper.ToStringWithCulture(Info.GetConstructorString())); + this.Write(";\r\n"); + bool memberAssignExists = false; + for (var memberIndex = 0; memberIndex <= Info.MaxKey; memberIndex++) { + var member = Info.GetMember(memberIndex); + if (member == null || !member.IsWritable || member.IsInitOnly || member.IsRequired || Info.ConstructorParameters.Any(p => p.Equals(member))) { continue; } + memberAssignExists = true; + this.Write("\t\t\tif (length <= "); + this.Write(this.ToStringHelper.ToStringWithCulture(memberIndex)); + this.Write(")\r\n\t\t\t{\r\n\t\t\t\tgoto MEMBER_ASSIGNMENT_END;\r\n\t\t\t}\r\n\r\n\t\t\t"); + this.Write(this.ToStringHelper.ToStringWithCulture(member.GetMemberAccess("____result"))); + this.Write(" = "); + this.Write(this.ToStringHelper.ToStringWithCulture(member.LocalVariableName)); + this.Write(";\r\n"); + } + if (memberAssignExists) { + this.Write("\r\n\t\tMEMBER_ASSIGNMENT_END:\r\n"); + } + } + + if (Info.HasIMessagePackSerializationCallbackReceiver) { + if (Info.NeedsCastOnAfter) { + this.Write("\t\t\t((MsgPack::IMessagePackSerializationCallbackReceiver)____result).OnAfterDeseri" + + "alize();\r\n"); + } else { + this.Write("\t\t\t____result.OnAfterDeserialize();\r\n"); + } + } + this.Write("\t\t\treader.Depth--;\r\n\t\t\treturn ____result;\r\n"); + } + this.Write("\t\t}\r\n\t}\r\n"); + } + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] + public class FormatterTemplateBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/src/MessagePack.SourceGenerator/Transforms/FormatterTemplate.tt b/src/MessagePack.SourceGenerator/Transforms/FormatterTemplate.tt new file mode 100644 index 000000000..2bd9a8a6d --- /dev/null +++ b/src/MessagePack.SourceGenerator/Transforms/FormatterTemplate.tt @@ -0,0 +1,141 @@ +<#@ template debug="false" hostspecific="false" linePragmas="false" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> + +#pragma warning disable CS8669 // We may leak nullable annotations into generated code. + +using MsgPack = global::MessagePack; + +<# using (this.EmitNestingTypesAndNamespaces(this.Write)) { #> + +<# bool isFormatterResolverNecessary = GeneratorUtilities.ShouldUseFormatterResolver(Info.Members);#> + internal sealed class <#= Info.Formatter.GetQualifiedName(Qualifiers.None) #> : MsgPack::Formatters.IMessagePackFormatter<<#= Info.DataType.GetQualifiedName(genericStyle: GenericParameterStyle.Identifiers) #>> +<# TransformUtilities.EmitTypeConstraints(Info.Formatter, this.Write); #> + { +<# foreach (var item in Info.Members) { #> +<# if (item.CustomFormatter != null) { #> + private readonly <#= item.CustomFormatter.InstanceTypeName.GetQualifiedName(genericStyle: GenericParameterStyle.Arguments) #> __<#= item.Name #>CustomFormatter__ = <#= item.CustomFormatter.InstanceExpression #>; +<# } #> +<# } #> + + public void Serialize(ref MsgPack::MessagePackWriter writer, <#= Info.DataType.GetQualifiedName(genericStyle: GenericParameterStyle.Identifiers) #> value, MsgPack::MessagePackSerializerOptions options) + { +<# if (Info.IsClass) { #> + if (value == null) + { + writer.WriteNil(); + return; + } + +<# } + + if (isFormatterResolverNecessary) { #> + MsgPack::IFormatterResolver formatterResolver = options.Resolver; +<# } + + if (Info.HasIMessagePackSerializationCallbackReceiver) { + if (Info.NeedsCastOnBefore) { #> + ((MsgPack::IMessagePackSerializationCallbackReceiver)value).OnBeforeSerialize(); +<# } else { #> + value.OnBeforeSerialize(); +<# } #> +<# } #> + writer.WriteArrayHeader(<#= Info.MaxKey + 1 #>); +<# for (var i = 0; i <= Info.MaxKey; i++) { + var member = Info.GetMember(i); + if (member == null) { #> + writer.WriteNil(); +<# } else { #> + <#= member.GetSerializeMethodString() #>; +<# } #> +<# } #> + } + + public <#= Info.DataType.GetQualifiedName(genericStyle: GenericParameterStyle.Identifiers) #> Deserialize(ref MsgPack::MessagePackReader reader, MsgPack::MessagePackSerializerOptions options) + { + if (reader.TryReadNil()) + { +<# if (Info.IsClass) { #> + return null; +<# } else { #> + throw new global::System.InvalidOperationException("typecode is null, struct not supported"); +<# } #> + } + +<# if (Info.MaxKey == -1 && !Info.HasIMessagePackSerializationCallbackReceiver) { #> + reader.Skip(); + return new <#= Info.GetConstructorString() #>; +<# } else { #> + options.Security.DepthStep(ref reader); +<# if (isFormatterResolverNecessary) { #> + MsgPack::IFormatterResolver formatterResolver = options.Resolver; +<# } #> + var length = reader.ReadArrayHeader(); +<# if (Info.MustDeserializeFieldsFirst) { + foreach (var member in Info.Members) { #> + var <#= member.LocalVariableName #> = default(<#= member.Type #>); +<# } +} else { #> + var ____result = new <#= Info.GetConstructorString() #>; +<# } #> + + for (int i = 0; i < length; i++) + { + switch (i) + { +<# for (var memberIndex = 0; memberIndex <= Info.MaxKey; memberIndex++) { + var member = Info.GetMember(memberIndex); + if (member == null) { continue; } #> + case <#= member.IntKey #>: +<# if (Info.MustDeserializeFieldsFirst) { #> + <#= member.LocalVariableName #> = <#= member.GetDeserializeMethodString() #>; +<# } else { + if (member.IsWritable) { #> + <#= member.GetMemberAccess("____result") #> = <#= member.GetDeserializeMethodString() #>; +<# } else { #> + reader.Skip(); +<# } #> +<# } #> + break; +<# } #> + default: + reader.Skip(); + break; + } + } + +<# if (Info.MustDeserializeFieldsFirst) { #> + var ____result = new <#= Info.GetConstructorString() #>; +<# bool memberAssignExists = false; + for (var memberIndex = 0; memberIndex <= Info.MaxKey; memberIndex++) { + var member = Info.GetMember(memberIndex); + if (member == null || !member.IsWritable || member.IsInitOnly || member.IsRequired || Info.ConstructorParameters.Any(p => p.Equals(member))) { continue; } + memberAssignExists = true;#> + if (length <= <#= memberIndex #>) + { + goto MEMBER_ASSIGNMENT_END; + } + + <#= member.GetMemberAccess("____result") #> = <#= member.LocalVariableName #>; +<# } #> +<# if (memberAssignExists) { #> + + MEMBER_ASSIGNMENT_END: +<# } + } + + if (Info.HasIMessagePackSerializationCallbackReceiver) { + if (Info.NeedsCastOnAfter) { #> + ((MsgPack::IMessagePackSerializationCallbackReceiver)____result).OnAfterDeserialize(); +<# } else { #> + ____result.OnAfterDeserialize(); +<# } #> +<# } #> + reader.Depth--; + return ____result; +<# } #> + } + } +<# } #> diff --git a/src/MessagePack.SourceGenerator/Transforms/GeneratorUtilities.cs b/src/MessagePack.SourceGenerator/Transforms/GeneratorUtilities.cs new file mode 100644 index 000000000..ab4ab5825 --- /dev/null +++ b/src/MessagePack.SourceGenerator/Transforms/GeneratorUtilities.cs @@ -0,0 +1,20 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace MessagePack.SourceGenerator.Transforms; + +internal static class GeneratorUtilities +{ + internal static bool ShouldUseFormatterResolver(MemberSerializationInfo[] infos) + { + foreach (var memberSerializationInfo in infos) + { + if (memberSerializationInfo.CustomFormatter is null && Array.IndexOf(AnalyzerUtilities.PrimitiveTypes, memberSerializationInfo.Type) == -1) + { + return true; + } + } + + return false; + } +} diff --git a/src/MessagePack.SourceGenerator/Transforms/IFormatterTemplate.cs b/src/MessagePack.SourceGenerator/Transforms/IFormatterTemplate.cs new file mode 100644 index 000000000..c1533962d --- /dev/null +++ b/src/MessagePack.SourceGenerator/Transforms/IFormatterTemplate.cs @@ -0,0 +1,15 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace MessagePack.SourceGenerator.Transforms; + +public interface IFormatterTemplate +{ + string FileName { get; } + + QualifiedNamedTypeName ResolverName { get; } + + ResolverRegisterInfo Info { get; } + + string TransformText(); +} diff --git a/src/MessagePack.SourceGenerator/Transforms/ResolverTemplate.cs b/src/MessagePack.SourceGenerator/Transforms/ResolverTemplate.cs new file mode 100644 index 000000000..96a158220 --- /dev/null +++ b/src/MessagePack.SourceGenerator/Transforms/ResolverTemplate.cs @@ -0,0 +1,404 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 17.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace MessagePack.SourceGenerator.Transforms +{ + using System.Linq; + using System.Text; + using System.Collections.Generic; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] + public partial class ResolverTemplate : ResolverTemplateBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("\r\nusing MsgPack = global::MessagePack;\r\n\r\n[assembly: MsgPack::Internal.GeneratedA" + + "ssemblyMessagePackResolverAttribute(typeof("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.ResolverName.GetQualifiedName(Qualifiers.Namespace))); + this.Write("), "); + this.Write(this.ToStringHelper.ToStringWithCulture(Version.Parse(ThisAssembly.AssemblyFileVersion).Major)); + this.Write(", "); + this.Write(this.ToStringHelper.ToStringWithCulture(Version.Parse(ThisAssembly.AssemblyFileVersion).Minor)); + this.Write(")]\r\n\r\n"); + using (TransformUtilities.EmitNestingTypesAndNamespaces(this.ResolverName, this.Write)) { + this.Write("\r\n/// A MessagePack resolver that uses generated formatters for types in" + + " this assembly.\r\npartial class "); + this.Write(this.ToStringHelper.ToStringWithCulture(ResolverName.Name)); + this.Write(" : MsgPack::IFormatterResolver\r\n{\r\n\t/// An instance of this resolver tha" + + "t only returns formatters specifically generated for types in this assembly.\r\n\tpublic static readonly MsgPack::IFormatterResolver Instance = new "); + this.Write(this.ToStringHelper.ToStringWithCulture(ResolverName.Name)); + this.Write("();\r\n\r\n\tprivate "); + this.Write(this.ToStringHelper.ToStringWithCulture(ResolverName.Name)); + this.Write(@"() + { + } + + public MsgPack::Formatters.IMessagePackFormatter GetFormatter() + { + return FormatterCache.Formatter; + } + + private static class FormatterCache + { + internal static readonly MsgPack::Formatters.IMessagePackFormatter Formatter; + + static FormatterCache() + { + var f = "); + this.Write(this.ToStringHelper.ToStringWithCulture(ResolverName.Name)); + this.Write("GetFormatterHelper.GetFormatter(typeof(T));\r\n\t\t\tif (f != null)\r\n\t\t\t{\r\n\t\t\t\tFormatt" + + "er = (MsgPack::Formatters.IMessagePackFormatter)f;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tprivate" + + " static class "); + this.Write(this.ToStringHelper.ToStringWithCulture(ResolverName.Name)); + this.Write("GetFormatterHelper\r\n\t{\r\n"); + + var constructedRegistrations = ConstructedTypeRegistrations.ToArray(); + if (constructedRegistrations.Length > 0) { + this.Write("\t\tprivate static readonly global::System.Collections.Generic.Dictionary closedTypeLookup = new global::System.Collections.Generic.Dicti" + + "onary("); + this.Write(this.ToStringHelper.ToStringWithCulture(constructedRegistrations.Length)); + this.Write(")\r\n\t\t{\r\n"); + for(var i = 0; i < constructedRegistrations.Length; i++) { + this.Write("\t\t\t{ typeof("); + this.Write(this.ToStringHelper.ToStringWithCulture(constructedRegistrations[i].DataType.GetQualifiedName(genericStyle: GenericParameterStyle.Arguments))); + this.Write("), "); + this.Write(this.ToStringHelper.ToStringWithCulture(i)); + this.Write(" },\r\n"); + } + this.Write("\t\t};\r\n"); + } + var openGenericRegistrations = OpenGenericRegistrations.ToArray(); + if (openGenericRegistrations.Length > 0) { + this.Write("\t\tprivate static readonly global::System.Collections.Generic.Dictionary openTypeLookup = new global::System.Collections.Generic.Diction" + + "ary("); + this.Write(this.ToStringHelper.ToStringWithCulture(openGenericRegistrations.Length)); + this.Write(")\r\n\t\t{\r\n"); + for(var i = 0; i < openGenericRegistrations.Length; i++) { + this.Write("\t\t\t{ typeof("); + this.Write(this.ToStringHelper.ToStringWithCulture(openGenericRegistrations[i].DataType.GetQualifiedName(genericStyle: GenericParameterStyle.TypeDefinition))); + this.Write("), "); + this.Write(this.ToStringHelper.ToStringWithCulture(i)); + this.Write(" },\r\n"); + } + this.Write("\t\t};\r\n"); + } + this.Write("\r\n\t\tinternal static object GetFormatter(global::System.Type t)\r\n\t\t{\r\n"); + if (constructedRegistrations.Length > 0) { + this.Write("\t\t\tif (closedTypeLookup.TryGetValue(t, out int closedKey))\r\n\t\t\t{\r\n\t\t\t\tswitch (clo" + + "sedKey)\r\n\t\t\t\t{\r\n"); + for(var i = 0; i < constructedRegistrations.Length; i++) { var x = constructedRegistrations[i]; + this.Write("\t\t\t\t\tcase "); + this.Write(this.ToStringHelper.ToStringWithCulture(i)); + this.Write(": return "); + this.Write(this.ToStringHelper.ToStringWithCulture(x.GetFormatterInstanceForResolver())); + this.Write(";\r\n"); + } + this.Write("\t\t\t\t\tdefault: return null; // unreachable\r\n\t\t\t\t};\r\n\t\t\t}\r\n"); + } + + if (openGenericRegistrations.Length > 0) { + this.Write("\t\t\tif (t.IsGenericType && openTypeLookup.TryGetValue(t.GetGenericTypeDefinition()" + + ", out int openKey))\r\n\t\t\t{\r\n\t\t\t\tswitch (openKey)\r\n\t\t\t\t{\r\n"); + for(var i = 0; i < openGenericRegistrations.Length; i++) { var x = openGenericRegistrations[i]; + this.Write("\t\t\t\t\tcase "); + this.Write(this.ToStringHelper.ToStringWithCulture(i)); + this.Write(": return global::System.Activator.CreateInstance(typeof("); + this.Write(this.ToStringHelper.ToStringWithCulture(x.GetFormatterNameForResolver(GenericParameterStyle.TypeDefinition))); + this.Write(").MakeGenericType(t.GenericTypeArguments));\r\n"); + } + this.Write("\t\t\t\t\tdefault: return null; // unreachable\r\n\t\t\t\t};\r\n\t\t\t}\r\n"); + } + this.Write("\r\n\t\t\treturn null;\r\n\t\t}\r\n\t}\r\n}\r\n\r\n"); + } + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] + public class ResolverTemplateBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/src/MessagePack.SourceGenerator/Transforms/ResolverTemplate.tt b/src/MessagePack.SourceGenerator/Transforms/ResolverTemplate.tt new file mode 100644 index 000000000..4a816aa96 --- /dev/null +++ b/src/MessagePack.SourceGenerator/Transforms/ResolverTemplate.tt @@ -0,0 +1,97 @@ +<#@ template debug="false" hostspecific="false" linePragmas="false" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> + +using MsgPack = global::MessagePack; + +[assembly: MsgPack::Internal.GeneratedAssemblyMessagePackResolverAttribute(typeof(<#= this.ResolverName.GetQualifiedName(Qualifiers.Namespace) #>), <#= Version.Parse(ThisAssembly.AssemblyFileVersion).Major #>, <#= Version.Parse(ThisAssembly.AssemblyFileVersion).Minor #>)] + +<# using (TransformUtilities.EmitNestingTypesAndNamespaces(this.ResolverName, this.Write)) { #> + +/// A MessagePack resolver that uses generated formatters for types in this assembly. +partial class <#= ResolverName.Name #> : MsgPack::IFormatterResolver +{ + /// An instance of this resolver that only returns formatters specifically generated for types in this assembly. + public static readonly MsgPack::IFormatterResolver Instance = new <#= ResolverName.Name #>(); + + private <#= ResolverName.Name #>() + { + } + + public MsgPack::Formatters.IMessagePackFormatter GetFormatter() + { + return FormatterCache.Formatter; + } + + private static class FormatterCache + { + internal static readonly MsgPack::Formatters.IMessagePackFormatter Formatter; + + static FormatterCache() + { + var f = <#= ResolverName.Name #>GetFormatterHelper.GetFormatter(typeof(T)); + if (f != null) + { + Formatter = (MsgPack::Formatters.IMessagePackFormatter)f; + } + } + } + + private static class <#= ResolverName.Name #>GetFormatterHelper + { +<# + var constructedRegistrations = ConstructedTypeRegistrations.ToArray(); + if (constructedRegistrations.Length > 0) { #> + private static readonly global::System.Collections.Generic.Dictionary closedTypeLookup = new global::System.Collections.Generic.Dictionary(<#= constructedRegistrations.Length #>) + { +<# for(var i = 0; i < constructedRegistrations.Length; i++) { #> + { typeof(<#= constructedRegistrations[i].DataType.GetQualifiedName(genericStyle: GenericParameterStyle.Arguments) #>), <#= i #> }, +<# } #> + }; +<# } + var openGenericRegistrations = OpenGenericRegistrations.ToArray(); + if (openGenericRegistrations.Length > 0) { #> + private static readonly global::System.Collections.Generic.Dictionary openTypeLookup = new global::System.Collections.Generic.Dictionary(<#= openGenericRegistrations.Length #>) + { +<# for(var i = 0; i < openGenericRegistrations.Length; i++) { #> + { typeof(<#= openGenericRegistrations[i].DataType.GetQualifiedName(genericStyle: GenericParameterStyle.TypeDefinition) #>), <#= i #> }, +<# } #> + }; +<# } #> + + internal static object GetFormatter(global::System.Type t) + { +<# if (constructedRegistrations.Length > 0) { #> + if (closedTypeLookup.TryGetValue(t, out int closedKey)) + { + switch (closedKey) + { +<# for(var i = 0; i < constructedRegistrations.Length; i++) { var x = constructedRegistrations[i]; #> + case <#= i #>: return <#= x.GetFormatterInstanceForResolver() #>; +<# } #> + default: return null; // unreachable + }; + } +<# } + + if (openGenericRegistrations.Length > 0) { #> + if (t.IsGenericType && openTypeLookup.TryGetValue(t.GetGenericTypeDefinition(), out int openKey)) + { + switch (openKey) + { +<# for(var i = 0; i < openGenericRegistrations.Length; i++) { var x = openGenericRegistrations[i]; #> + case <#= i #>: return global::System.Activator.CreateInstance(typeof(<#= x.GetFormatterNameForResolver(GenericParameterStyle.TypeDefinition) #>).MakeGenericType(t.GenericTypeArguments)); +<# } #> + default: return null; // unreachable + }; + } +<# } #> + + return null; + } + } +} + +<# } #> diff --git a/src/MessagePack.SourceGenerator/Transforms/StringKey/EmbedStringHelper.cs b/src/MessagePack.SourceGenerator/Transforms/StringKey/EmbedStringHelper.cs new file mode 100644 index 000000000..590f9e53c --- /dev/null +++ b/src/MessagePack.SourceGenerator/Transforms/StringKey/EmbedStringHelper.cs @@ -0,0 +1,85 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Text; + +namespace MessagePack.SourceGenerator.Transforms; + +public static class EmbedStringHelper +{ + public static readonly Encoding Utf8 = new UTF8Encoding(false); + + public static string ToByteArrayString(byte[] binary) + { + var headerLength = GetHeaderLength(binary.Length); + Span header = stackalloc byte[headerLength]; + EmbedHeader(binary.Length, header); + var buffer = new StringBuilder().Append("new byte[").Append(headerLength).Append(" + ").Append(binary.Length).Append("] { ").Append(header[0]); + foreach (var b in header.Slice(1)) + { + buffer.Append(", ").Append(b); + } + + foreach (var b in binary) + { + buffer.Append(", ").Append(b); + } + + return buffer.Append(" }").ToString(); + } + + public static int GetHeaderLength(int byteCount) + { + if (byteCount <= 31) + { + return 1; + } + + if (byteCount <= byte.MaxValue) + { + return 2; + } + + return byteCount <= ushort.MaxValue ? 3 : 5; + } + + public static void EmbedHeader(int byteCount, Span destination) + { + if (byteCount <= 31) + { + destination[0] = (byte)(0xa0 | byteCount); + return; + } + + if (byteCount <= byte.MaxValue) + { + destination[0] = 0xd9; + destination[1] = unchecked((byte)byteCount); + return; + } + + if (byteCount <= ushort.MaxValue) + { + destination[0] = 0xda; + destination[1] = unchecked((byte)(byteCount >> 8)); + destination[2] = unchecked((byte)byteCount); + return; + } + + destination[0] = 0xdb; + destination[1] = unchecked((byte)(byteCount >> 24)); + destination[2] = unchecked((byte)(byteCount >> 16)); + destination[3] = unchecked((byte)(byteCount >> 8)); + destination[4] = unchecked((byte)byteCount); + } + + public static byte[] GetEncodedStringBytes(string value) + { + var byteCount = Utf8.GetByteCount(value); + var headerLength = GetHeaderLength(byteCount); + var bytes = new byte[headerLength + byteCount]; + EmbedHeader(byteCount, bytes); + Utf8.GetBytes(value, 0, value.Length, bytes, headerLength); + return bytes; + } +} diff --git a/src/MessagePack.SourceGenerator/Transforms/StringKey/StringKeyFormatterDeserializeHelper.cs b/src/MessagePack.SourceGenerator/Transforms/StringKey/StringKeyFormatterDeserializeHelper.cs new file mode 100644 index 000000000..c2975e485 --- /dev/null +++ b/src/MessagePack.SourceGenerator/Transforms/StringKey/StringKeyFormatterDeserializeHelper.cs @@ -0,0 +1,247 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Text; +using MessagePack.Internal; + +namespace MessagePack.SourceGenerator.Transforms; + +internal static class StringKeyFormatterDeserializeHelper +{ + public static string Classify(ObjectSerializationInfo objectSerializationInfo, string indent, bool canOverwrite) + { + var memberArray = objectSerializationInfo.Members; + var buffer = new StringBuilder(); + foreach (var memberInfoTuples in memberArray.Select(member => new MemberInfoTuple(member, IsConstructorParameter(objectSerializationInfo, member))).GroupBy(member => member.Binary.Length)) + { + var binaryLength = memberInfoTuples.Key; + var keyLength = binaryLength >> 3; + keyLength += keyLength << 3 == binaryLength ? 0 : 1; + + buffer.Append(indent).Append("case ").Append(binaryLength).Append(":\r\n"); + ClassifyRecursion(buffer, indent, 1, keyLength, memberInfoTuples, canOverwrite); + } + + return buffer.ToString(); + } + + private static bool IsConstructorParameter(ObjectSerializationInfo objectSerializationInfo, MemberSerializationInfo member) + { + foreach (var parameter in objectSerializationInfo.ConstructorParameters) + { + if (parameter.Equals(member)) + { + return true; + } + } + + return false; + } + + private static void Assign(StringBuilder buffer, in MemberInfoTuple member, bool canOverwrite, string indent, string tab, int tabCount) + { + if (member.Info.IsWritable || member.IsConstructorParameter) + { + if (canOverwrite) + { + buffer.Append(member.Info.GetMemberAccess("____result")).Append(" = "); + } + else + { + if (!member.IsConstructorParameter && !member.Info.IsInitOnly && !member.Info.IsRequired) + { + buffer.Append("__").Append(member.Info.Name).Append("__IsInitialized = true;\r\n").Append(indent); + for (var i = 0; i < tabCount; i++) + { + buffer.Append(tab); + } + } + + buffer.Append("__").Append(member.Info.Name).Append("__ = "); + } + + buffer.Append(member.Info.GetDeserializeMethodString()).Append(";\r\n"); + } + else + { + buffer.Append("reader.Skip();\r\n"); + } + } + + private static void ClassifyRecursion(StringBuilder buffer, string indent, int tabCount, int keyLength, IEnumerable memberCollection, bool canOverwrite) + { + const string Tab = " "; + buffer.Append(indent); + for (var i = 0; i < tabCount; i++) + { + buffer.Append(Tab); + } + + var memberArray = memberCollection.ToArray(); + if (memberArray.Length == 1) + { + var member = memberArray[0]; + EmbedOne(buffer, indent, tabCount, member, canOverwrite); + return; + } + + buffer.Append("switch (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey))\r\n").Append(indent); + for (var i = 0; i < tabCount; i++) + { + buffer.Append(Tab); + } + + buffer.Append("{\r\n" + Tab).Append(indent); + for (var i = 0; i < tabCount; i++) + { + buffer.Append(Tab); + } + + buffer.Append("default: goto FAIL;"); + + foreach (var grouping in memberArray.GroupBy(member => member.Key[tabCount - 1])) + { + buffer.Append("\r\n" + Tab).Append(indent); + for (var i = 0; i < tabCount; i++) + { + buffer.Append(Tab); + } + + buffer.Append("case ").Append(grouping.Key).Append("UL:\r\n"); + + if (tabCount == keyLength) + { + buffer.Append(Tab + Tab).Append(indent); + for (var i = 0; i < tabCount; i++) + { + buffer.Append(Tab); + } + + var member = grouping.Single(); + Assign(buffer, member, canOverwrite, indent, Tab, tabCount + 2); + buffer.Append(Tab + Tab).Append(indent); + for (var i = 0; i < tabCount; i++) + { + buffer.Append(Tab); + } + + buffer.Append("continue;"); + continue; + } + + ClassifyRecursion(buffer, indent + Tab, tabCount + 1, keyLength, grouping, canOverwrite); + } + + buffer.Append("\r\n").Append(indent); + for (var i = 0; i < tabCount; i++) + { + buffer.Append(Tab); + } + + buffer.Append("}\r\n"); + } + + private static void EmbedOne(StringBuilder buffer, string indent, int tabCount, in MemberInfoTuple member, bool canOverwrite) + { + const string Tab = " "; + var binary = member.Binary.AsSpan((tabCount - 1) << 3); + + switch (binary.Length) + { + case 1: + buffer.Append("if (stringKey[0] != ").Append(binary[0]); + break; + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + buffer.Append("if (global::MessagePack.Internal.AutomataKeyGen.GetKey(ref stringKey) != ").Append(member.Key[tabCount - 1]).Append("UL"); + break; + default: + EmbedSequenceEqual(buffer, member, (tabCount << 3) - 8); + break; + } + + buffer.Append(") { goto FAIL; }\r\n\r\n").Append(indent); + for (var i = 0; i < tabCount; i++) + { + buffer.Append(Tab); + } + + Assign(buffer, member, canOverwrite, indent, Tab, tabCount); + buffer.Append(indent); + for (var i = 0; i < tabCount; i++) + { + buffer.Append(Tab); + } + + buffer.Append("continue;\r\n"); + } + + private static void EmbedSequenceEqual(StringBuilder buffer, MemberInfoTuple member, int startPosition) + { + buffer + .Append("if (!global::System.MemoryExtensions.SequenceEqual(stringKey, GetSpan_") + .Append(member.Info.Name) + .Append("().Slice(") + .Append(EmbedStringHelper.GetHeaderLength(member.Binary.Length)); + + if (startPosition != 0) + { + buffer.Append(" + ").Append(startPosition); + } + + buffer.Append("))"); + } +} + +internal readonly struct MemberInfoTuple : IComparable +{ + public readonly MemberSerializationInfo Info; + public readonly bool IsConstructorParameter; + public readonly byte[] Binary; + public readonly ulong[] Key; + + public MemberInfoTuple(MemberSerializationInfo info, bool isConstructorParameter) + { + Info = info; + IsConstructorParameter = isConstructorParameter; + Binary = EmbedStringHelper.Utf8.GetBytes(info.StringKey); + ReadOnlySpan span = Binary; + var keyLength = Binary.Length >> 3; + keyLength += keyLength << 3 == Binary.Length ? 0 : 1; + Key = new ulong[keyLength]; + for (var i = 0; i < Key.Length; i++) + { + Key[i] = AutomataKeyGen.GetKey(ref span); + } + } + + public int CompareTo(MemberInfoTuple other) + { + if (Info == other.Info) + { + return 0; + } + + var c = Binary.Length.CompareTo(other.Binary.Length); + if (c != 0) + { + return c; + } + + for (var i = 0; i < Key.Length; i++) + { + c = Key[i].CompareTo(other.Key[i]); + if (c != 0) + { + return c; + } + } + + return 0; + } +} diff --git a/src/MessagePack.SourceGenerator/Transforms/StringKey/StringKeyFormatterTemplate.cs b/src/MessagePack.SourceGenerator/Transforms/StringKey/StringKeyFormatterTemplate.cs new file mode 100644 index 000000000..640b6e99b --- /dev/null +++ b/src/MessagePack.SourceGenerator/Transforms/StringKey/StringKeyFormatterTemplate.cs @@ -0,0 +1,450 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 17.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace MessagePack.SourceGenerator.Transforms +{ + using System; + using System.Linq; + using System.Collections.Generic; + using MessagePack.SourceGenerator.CodeAnalysis; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] + public partial class StringKeyFormatterTemplate : StringKeyFormatterTemplateBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("\r\nusing MsgPack = global::MessagePack;\r\n\r\n"); + var list = new List>(); + foreach (var member in Info.Members) { + var binary = EmbedStringHelper.Utf8.GetBytes(member.StringKey); + list.Add(new ValueTuple(member, binary)); + } + + bool isFormatterResolverNecessary = GeneratorUtilities.ShouldUseFormatterResolver(Info.Members); + using (this.EmitNestingTypesAndNamespaces(this.Write)) { + this.Write("\tinternal sealed class "); + this.Write(this.ToStringHelper.ToStringWithCulture(Info.Formatter.GetQualifiedName(Qualifiers.None))); + this.Write(" : global::MessagePack.Formatters.IMessagePackFormatter<"); + this.Write(this.ToStringHelper.ToStringWithCulture(Info.DataType.GetQualifiedName())); + this.Write(">\r\n"); + TransformUtilities.EmitTypeConstraints(Info.Formatter, this.Write); + this.Write("\t{\r\n"); + foreach (var item in Info.Members) { + if (item.CustomFormatter != null) { + this.Write("\t\tprivate readonly "); + this.Write(this.ToStringHelper.ToStringWithCulture(item.CustomFormatter.Name.GetQualifiedName())); + this.Write(" __"); + this.Write(this.ToStringHelper.ToStringWithCulture(item.Name)); + this.Write("CustomFormatter__ = "); + this.Write(this.ToStringHelper.ToStringWithCulture(item.CustomFormatter.InstanceExpression)); + this.Write(";\r\n"); + } + } + for (var i = 0; i < list.Count; i++) { + var member = list[i].Item1; + var binary = list[i].Item2; + this.Write("\t\t// "); + this.Write(this.ToStringHelper.ToStringWithCulture(member.StringKey)); + this.Write("\r\n\t\tprivate static global::System.ReadOnlySpan GetSpan_"); + this.Write(this.ToStringHelper.ToStringWithCulture(member.UniqueIdentifier)); + this.Write("() => "); + this.Write(this.ToStringHelper.ToStringWithCulture(EmbedStringHelper.ToByteArrayString(binary))); + this.Write(";\r\n"); + } + if (list.Count != 0) { + this.Write("\r\n"); + } + this.Write("\t\tpublic void Serialize(ref global::MessagePack.MessagePackWriter writer, "); + this.Write(this.ToStringHelper.ToStringWithCulture(Info.DataType.GetQualifiedName())); + this.Write(" value, global::MessagePack.MessagePackSerializerOptions options)\r\n\t\t{\r\n"); + if (Info.IsClass) { + this.Write("\t\t\tif (value is null)\r\n\t\t\t{\r\n\t\t\t\twriter.WriteNil();\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n"); + } + + if (isFormatterResolverNecessary) { + this.Write("\t\t\tvar formatterResolver = options.Resolver;\r\n"); + } + + if (Info.HasIMessagePackSerializationCallbackReceiver) { + if (Info.NeedsCastOnBefore) { + this.Write("\t\t\t((global::MessagePack.IMessagePackSerializationCallbackReceiver)value).OnBefor" + + "eSerialize();\r\n"); + } else { + this.Write("\t\t\tvalue.OnBeforeSerialize();\r\n"); + } + } + this.Write("\t\t\twriter.WriteMapHeader("); + this.Write(this.ToStringHelper.ToStringWithCulture(list.Count)); + this.Write(");\r\n"); + foreach (var memberAndBinary in list) { + var member = memberAndBinary.Item1; + this.Write("\t\t\twriter.WriteRaw(GetSpan_"); + this.Write(this.ToStringHelper.ToStringWithCulture(member.UniqueIdentifier)); + this.Write("());\r\n\t\t\t"); + this.Write(this.ToStringHelper.ToStringWithCulture(member.GetSerializeMethodString())); + this.Write(";\r\n"); + } + this.Write("\t\t}\r\n\r\n\t\tpublic "); + this.Write(this.ToStringHelper.ToStringWithCulture(Info.DataType.GetQualifiedName())); + this.Write(" Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePac" + + "k.MessagePackSerializerOptions options)\r\n\t\t{\r\n\t\t\tif (reader.TryReadNil())\r\n\t\t\t{\r" + + "\n"); + if (Info.IsClass) { + this.Write("\t\t\t\treturn null;\r\n"); + } else { + this.Write("\t\t\t\tthrow new global::System.InvalidOperationException(\"typecode is null, struct " + + "not supported\");\r\n"); + } + this.Write("\t\t\t}\r\n\r\n"); + if (Info.Members.Length == 0) { + this.Write("\t\t\treader.Skip();\r\n\t\t\tvar ____result = new "); + this.Write(this.ToStringHelper.ToStringWithCulture(Info.GetConstructorString())); + this.Write(";\r\n"); + } else { + this.Write("\t\t\toptions.Security.DepthStep(ref reader);\r\n"); + if (isFormatterResolverNecessary) { + this.Write("\t\t\tvar formatterResolver = options.Resolver;\r\n"); + } + this.Write("\t\t\tvar length = reader.ReadMapHeader();\r\n"); + if (Info.MustDeserializeFieldsFirst) { + foreach (var member in Info.Members.Where(x => x.IsWritable || Info.ConstructorParameters.Any(p => p.Equals(x)))) { + // Until C# allows for optionally setting init-only properties (https://github.com/dotnet/csharplang/issues/6117) + // we will unconditionally set them, and thus have no reason to track whether the local variable has been initialized. + if (!member.IsInitOnly && !member.IsRequired && !Info.ConstructorParameters.Any(p => p.Equals(member))) { + this.Write("\t\t\tvar "); + this.Write(this.ToStringHelper.ToStringWithCulture(member.LocalVariableName)); + this.Write("IsInitialized = false;\r\n"); + } + this.Write("\t\t\tvar "); + this.Write(this.ToStringHelper.ToStringWithCulture(member.LocalVariableName)); + this.Write(" = default("); + this.Write(this.ToStringHelper.ToStringWithCulture(member.Type)); + this.Write(");\r\n"); + } + } else { + this.Write("\t\t\tvar ____result = new "); + this.Write(this.ToStringHelper.ToStringWithCulture(Info.GetConstructorString())); + this.Write(";\r\n"); + } + this.Write("\r\n\t\t\tfor (int i = 0; i < length; i++)\r\n\t\t\t{\r\n\t\t\t\tvar stringKey = global::MessageP" + + "ack.Internal.CodeGenHelpers.ReadStringSpan(ref reader);\r\n\t\t\t\tswitch (stringKey.L" + + "ength)\r\n\t\t\t\t{\r\n\t\t\t\t\tdefault:\r\n\t\t\t\t\tFAIL:\r\n\t\t\t\t\t reader.Skip();\r\n\t\t\t\t\t continue" + + ";\r\n"); + this.Write(this.ToStringHelper.ToStringWithCulture(StringKeyFormatterDeserializeHelper.Classify(Info, " ", !Info.MustDeserializeFieldsFirst))); + this.Write("\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n"); + if (Info.MustDeserializeFieldsFirst) { + this.Write("\t\t\tvar ____result = new "); + this.Write(this.ToStringHelper.ToStringWithCulture(Info.GetConstructorString())); + this.Write(";\r\n"); + foreach (var member in Info.Members.Where(x => x.IsWritable && !x.IsInitOnly && !x.IsRequired && !Info.ConstructorParameters.Any(p => p.Equals(x)))) { + this.Write("\t\t\tif ("); + this.Write(this.ToStringHelper.ToStringWithCulture(member.LocalVariableName)); + this.Write("IsInitialized)\r\n\t\t\t{\r\n\t\t\t\t"); + this.Write(this.ToStringHelper.ToStringWithCulture(member.GetMemberAccess("____result"))); + this.Write(" = "); + this.Write(this.ToStringHelper.ToStringWithCulture(member.LocalVariableName)); + this.Write(";\r\n\t\t\t}\r\n\r\n"); + } + } + } + if (Info.HasIMessagePackSerializationCallbackReceiver) { + if (Info.NeedsCastOnAfter) { + this.Write("\t\t\t((global::MessagePack.IMessagePackSerializationCallbackReceiver)____result).On" + + "AfterDeserialize();\r\n"); + } else { + this.Write("\t\t\t____result.OnAfterDeserialize();\r\n"); + } + } + if (Info.Members.Length != 0) { + this.Write("\t\t\treader.Depth--;\r\n"); + } + this.Write("\t\t\treturn ____result;\r\n\t\t}\r\n\t}\r\n\r\n"); + } + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] + public class StringKeyFormatterTemplateBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/src/MessagePack.SourceGenerator/Transforms/StringKey/StringKeyFormatterTemplate.tt b/src/MessagePack.SourceGenerator/Transforms/StringKey/StringKeyFormatterTemplate.tt new file mode 100644 index 000000000..c81a3c376 --- /dev/null +++ b/src/MessagePack.SourceGenerator/Transforms/StringKey/StringKeyFormatterTemplate.tt @@ -0,0 +1,136 @@ +<#@ template debug="false" hostspecific="false" linePragmas="false" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ import namespace="MessagePack.SourceGenerator.CodeAnalysis" #> + +using MsgPack = global::MessagePack; + +<# var list = new List>(); + foreach (var member in Info.Members) { + var binary = EmbedStringHelper.Utf8.GetBytes(member.StringKey); + list.Add(new ValueTuple(member, binary)); + } + + bool isFormatterResolverNecessary = GeneratorUtilities.ShouldUseFormatterResolver(Info.Members); #> +<# using (this.EmitNestingTypesAndNamespaces(this.Write)) { #> + internal sealed class <#= Info.Formatter.GetQualifiedName(Qualifiers.None) #> : global::MessagePack.Formatters.IMessagePackFormatter<<#= Info.DataType.GetQualifiedName() #>> +<# TransformUtilities.EmitTypeConstraints(Info.Formatter, this.Write); #> + { +<# foreach (var item in Info.Members) { #> +<# if (item.CustomFormatter != null) { #> + private readonly <#= item.CustomFormatter.Name.GetQualifiedName() #> __<#= item.Name #>CustomFormatter__ = <#= item.CustomFormatter.InstanceExpression #>; +<# } #> +<# } #> +<# for (var i = 0; i < list.Count; i++) { + var member = list[i].Item1; + var binary = list[i].Item2; #> + // <#= member.StringKey #> + private static global::System.ReadOnlySpan GetSpan_<#= member.UniqueIdentifier #>() => <#= EmbedStringHelper.ToByteArrayString(binary) #>; +<# } #> +<# if (list.Count != 0) { #> + +<# } #> + public void Serialize(ref global::MessagePack.MessagePackWriter writer, <#= Info.DataType.GetQualifiedName() #> value, global::MessagePack.MessagePackSerializerOptions options) + { +<# if (Info.IsClass) { #> + if (value is null) + { + writer.WriteNil(); + return; + } + +<# } + + if (isFormatterResolverNecessary) { #> + var formatterResolver = options.Resolver; +<# } + + if (Info.HasIMessagePackSerializationCallbackReceiver) { + if (Info.NeedsCastOnBefore) { #> + ((global::MessagePack.IMessagePackSerializationCallbackReceiver)value).OnBeforeSerialize(); +<# } else { #> + value.OnBeforeSerialize(); +<# } #> +<# } #> + writer.WriteMapHeader(<#= list.Count #>); +<# foreach (var memberAndBinary in list) { + var member = memberAndBinary.Item1; #> + writer.WriteRaw(GetSpan_<#= member.UniqueIdentifier #>()); + <#= member.GetSerializeMethodString() #>; +<# } #> + } + + public <#= Info.DataType.GetQualifiedName() #> Deserialize(ref global::MessagePack.MessagePackReader reader, global::MessagePack.MessagePackSerializerOptions options) + { + if (reader.TryReadNil()) + { +<# if (Info.IsClass) { #> + return null; +<# } else { #> + throw new global::System.InvalidOperationException("typecode is null, struct not supported"); +<# } #> + } + +<# if (Info.Members.Length == 0) { #> + reader.Skip(); + var ____result = new <#= Info.GetConstructorString() #>; +<# } else { #> + options.Security.DepthStep(ref reader); +<# if (isFormatterResolverNecessary) { #> + var formatterResolver = options.Resolver; +<# } #> + var length = reader.ReadMapHeader(); +<# if (Info.MustDeserializeFieldsFirst) { + foreach (var member in Info.Members.Where(x => x.IsWritable || Info.ConstructorParameters.Any(p => p.Equals(x)))) { + // Until C# allows for optionally setting init-only properties (https://github.com/dotnet/csharplang/issues/6117) + // we will unconditionally set them, and thus have no reason to track whether the local variable has been initialized. + if (!member.IsInitOnly && !member.IsRequired && !Info.ConstructorParameters.Any(p => p.Equals(member))) { #> + var <#= member.LocalVariableName #>IsInitialized = false; +<# } #> + var <#= member.LocalVariableName #> = default(<#= member.Type #>); +<# } #> +<# } else { #> + var ____result = new <#= Info.GetConstructorString() #>; +<# } #> + + for (int i = 0; i < length; i++) + { + var stringKey = global::MessagePack.Internal.CodeGenHelpers.ReadStringSpan(ref reader); + switch (stringKey.Length) + { + default: + FAIL: + reader.Skip(); + continue; +<#= StringKeyFormatterDeserializeHelper.Classify(Info, " ", !Info.MustDeserializeFieldsFirst) #> + } + } + +<# if (Info.MustDeserializeFieldsFirst) { #> + var ____result = new <#= Info.GetConstructorString() #>; +<# foreach (var member in Info.Members.Where(x => x.IsWritable && !x.IsInitOnly && !x.IsRequired && !Info.ConstructorParameters.Any(p => p.Equals(x)))) { #> + if (<#= member.LocalVariableName #>IsInitialized) + { + <#= member.GetMemberAccess("____result") #> = <#= member.LocalVariableName #>; + } + +<# } #> +<# } #> +<# } #> +<# if (Info.HasIMessagePackSerializationCallbackReceiver) { + if (Info.NeedsCastOnAfter) { #> + ((global::MessagePack.IMessagePackSerializationCallbackReceiver)____result).OnAfterDeserialize(); +<# } else { #> + ____result.OnAfterDeserialize(); +<# } #> +<# } #> +<# if (Info.Members.Length != 0) { #> + reader.Depth--; +<# } #> + return ____result; + } + } + +<# } #> diff --git a/src/MessagePack.SourceGenerator/Transforms/TemplatePartials.cs b/src/MessagePack.SourceGenerator/Transforms/TemplatePartials.cs new file mode 100644 index 000000000..10394d68a --- /dev/null +++ b/src/MessagePack.SourceGenerator/Transforms/TemplatePartials.cs @@ -0,0 +1,116 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma warning disable SA1402 // File may only contain a single type + +namespace MessagePack.SourceGenerator.Transforms; + +public partial class FormatterTemplate : IFormatterTemplate +{ + public FormatterTemplate(AnalyzerOptions options, ObjectSerializationInfo info) + { + this.Options = options; + this.Info = info; + } + + public AnalyzerOptions Options { get; } + + public QualifiedNamedTypeName ResolverName => this.Options.Generator.Resolver.Name; + + public ObjectSerializationInfo Info { get; } + + ResolverRegisterInfo IFormatterTemplate.Info => this.Info; + + public string FileName => $"{this.Info.FileNameHint}.g.cs"; +} + +public partial class StringKeyFormatterTemplate : IFormatterTemplate +{ + public StringKeyFormatterTemplate(AnalyzerOptions options, ObjectSerializationInfo info) + { + this.Options = options; + this.Info = info; + } + + public AnalyzerOptions Options { get; } + + public QualifiedNamedTypeName ResolverName => this.Options.Generator.Resolver.Name; + + public ObjectSerializationInfo Info { get; } + + ResolverRegisterInfo IFormatterTemplate.Info => this.Info; + + public string FileName => $"{this.Info.FileNameHint}.g.cs"; +} + +public partial class ResolverTemplate +{ + public ResolverTemplate(AnalyzerOptions options, IReadOnlyList registerInfos) + { + this.Options = options; + this.AllRegisterRegistrations = registerInfos; + } + + public AnalyzerOptions Options { get; init; } + + public QualifiedNamedTypeName ResolverName => this.Options.Generator.Resolver.Name; + + public IReadOnlyList AllRegisterRegistrations { get; } + + public IEnumerable OpenGenericRegistrations => this.AllRegisterRegistrations.Where(r => r.IsUnboundGenericType); + + public IEnumerable ConstructedTypeRegistrations => this.AllRegisterRegistrations.Where(r => !r.IsUnboundGenericType); + + public string FileName => $"{this.ResolverName.GetQualifiedName(Qualifiers.Namespace, GenericParameterStyle.ArityOnly)}.g.cs"; +} + +public partial class EnumTemplate : IFormatterTemplate +{ + public EnumTemplate(AnalyzerOptions options, EnumSerializationInfo info) + { + this.Options = options; + this.Info = info; + } + + public AnalyzerOptions Options { get; } + + public QualifiedNamedTypeName ResolverName => this.Options.Generator.Resolver.Name; + + public EnumSerializationInfo Info { get; } + + ResolverRegisterInfo IFormatterTemplate.Info => this.Info; + + public string FileName => $"{this.Info.FileNameHint}.g.cs"; +} + +public partial class UnionTemplate : IFormatterTemplate +{ + public UnionTemplate(AnalyzerOptions options, UnionSerializationInfo info) + { + this.Options = options; + this.Info = info; + } + + public AnalyzerOptions Options { get; } + + public QualifiedNamedTypeName ResolverName => this.Options.Generator.Resolver.Name; + + public UnionSerializationInfo Info { get; } + + ResolverRegisterInfo IFormatterTemplate.Info => this.Info; + + public string FileName => $"{this.Info.FileNameHint}.g.cs"; +} + +public partial class CompositeResolverTemplate : IFormatterTemplate +{ + public string FileName => $"{this.ResolverName.Name}.g.cs"; + + public required QualifiedNamedTypeName ResolverName { get; init; } + + ResolverRegisterInfo IFormatterTemplate.Info => throw new NotImplementedException(); + + public required string[] ResolverInstanceExpressions { get; init; } + + public required string[] FormatterInstanceExpressions { get; init; } +} diff --git a/src/MessagePack.SourceGenerator/Transforms/TransformUtilities.cs b/src/MessagePack.SourceGenerator/Transforms/TransformUtilities.cs new file mode 100644 index 000000000..78518831b --- /dev/null +++ b/src/MessagePack.SourceGenerator/Transforms/TransformUtilities.cs @@ -0,0 +1,136 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; + +namespace MessagePack.SourceGenerator.Transforms; + +internal static class TransformUtilities +{ + internal static IDisposable? EmitNestingTypesAndNamespaces(this IFormatterTemplate template, Action writer) + => EmitNestingTypesAndNamespaces(template.Info.Formatter, writer); + + internal static IDisposable? EmitNestingTypesAndNamespaces(QualifiedTypeName nestedType, Action writer) + => nestedType is QualifiedNamedTypeName { Container: { } container } ? EmitNestingTypesAndNamespaces(container, writer) : null; + + internal static IDisposable? EmitNestingTypesAndNamespaces(TypeContainer container, Action writer) + { + switch (container) + { + case NamespaceTypeContainer { Namespace: string ns }: + writer($"namespace {ns} {{\r\n"); + return new DisposeAction(() => writer("}\r\n")); + case NestingTypeContainer { NestingType: { } nestingType }: + Stack reverseAction = new(); + + // Start with outer-most type. + if (nestingType.Container is not null) + { + reverseAction.Push(EmitNestingTypesAndNamespaces(nestingType.Container, writer)); + } + + var kind = nestingType.Kind switch + { + TypeKind.Class when nestingType.IsRecord => "record", // C# 9 doesn't want to see `record class`. + TypeKind.Class => "class", + TypeKind.Struct when nestingType.IsRecord => "record struct", + TypeKind.Struct => "struct", + TypeKind.Interface => "interface", + _ => throw new NotSupportedException("Unsupported kind: " + nestingType.Kind), + }; + + var accessModifier = nestingType.AccessModifier switch + { + Accessibility.Public => "public", + Accessibility.Internal => "internal", + Accessibility.Private => "private", + _ => null, + }; + if (accessModifier is not null) + { + writer(accessModifier); + writer($" "); + } + + writer($"partial {kind} {nestingType.GetQualifiedName(Qualifiers.None, GenericParameterStyle.Identifiers)}"); + bool anyConstraints = EmitTypeConstraints(nestingType, writer, leadingLineFeedIfAny: true); + + writer(anyConstraints ? "{" : " {\r\n"); + reverseAction.Push(new DisposeAction(() => writer("}\r\n"))); + + return new DisposeAction(() => + { + while (reverseAction.Count > 0) + { + reverseAction.Pop()?.Dispose(); + } + }); + default: + throw new NotSupportedException(); + } + } + + internal static bool EmitTypeConstraints(QualifiedNamedTypeName target, Action writer, bool leadingLineFeedIfAny = false) + { + bool anyConstraints = false; + foreach (GenericTypeParameterInfo typeParameter in target.TypeParameters) + { + if (typeParameter.HasConstraints) + { + if (leadingLineFeedIfAny && !anyConstraints) + { + writer("\r\n"); + } + + anyConstraints = true; + writer($"\t\twhere {typeParameter.Name} : {typeParameter.Constraints}\r\n"); + } + } + + return anyConstraints; + } + + internal static IDisposable? EmitClassesForNamespace(this IFormatterTemplate template, out string formatterVisibility, Action writer) + { + if (template.Info.DataType is not QualifiedNamedTypeName { Container: NamespaceTypeContainer { Namespace: string ns } }) + { + formatterVisibility = "private"; + return null; + } + + int depth = 0; + foreach (string segment in ns.Split('.')) + { + string visibility = depth == 0 ? "private" : "internal"; + writer($"{visibility} partial class {segment} {{ "); + depth++; + } + + writer("\r\n"); + + formatterVisibility = "internal"; + return new DisposeAction(() => + { + for (int i = 0; i < depth; i++) + { + writer("}"); + } + }); + } + + private class DisposeAction : IDisposable + { + private Action? disposeAction; + + internal DisposeAction(Action? action) + { + this.disposeAction = action; + } + + public void Dispose() + { + this.disposeAction?.Invoke(); + this.disposeAction = null; + } + } +} diff --git a/src/MessagePack.SourceGenerator/Transforms/UnionTemplate.cs b/src/MessagePack.SourceGenerator/Transforms/UnionTemplate.cs new file mode 100644 index 000000000..12ce6a290 --- /dev/null +++ b/src/MessagePack.SourceGenerator/Transforms/UnionTemplate.cs @@ -0,0 +1,393 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 17.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace MessagePack.SourceGenerator.Transforms +{ + using System.Linq; + using System.Text; + using System.Collections.Generic; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] + public partial class UnionTemplate : UnionTemplateBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("\r\nusing MsgPack = global::MessagePack;\r\n\r\n"); + using (this.EmitNestingTypesAndNamespaces(this.Write)) { + this.Write("\tinternal sealed class "); + this.Write(this.ToStringHelper.ToStringWithCulture(Info.Formatter.GetQualifiedName(Qualifiers.None))); + this.Write(": MsgPack::Formatters.IMessagePackFormatter<"); + this.Write(this.ToStringHelper.ToStringWithCulture(Info.DataType.GetQualifiedName())); + this.Write(@"> + { + private readonly global::System.Collections.Generic.Dictionary> typeToKeyAndJumpMap; + private readonly global::System.Collections.Generic.Dictionary keyToJumpMap; + + public "); + this.Write(this.ToStringHelper.ToStringWithCulture(Info.Formatter.Name)); + this.Write("()\r\n\t\t{\r\n\t\t\tthis.typeToKeyAndJumpMap = new global::System.Collections.Generic.Dic" + + "tionary>("); + this.Write(this.ToStringHelper.ToStringWithCulture(Info.SubTypes.Length)); + this.Write(", MsgPack::Internal.RuntimeTypeHandleEqualityComparer.Default)\r\n\t\t\t{\r\n"); + for(var i = 0; i < Info.SubTypes.Length; i++) { var item = Info.SubTypes[i]; + this.Write("\t\t\t\t{ typeof("); + this.Write(this.ToStringHelper.ToStringWithCulture(item.Type)); + this.Write(").TypeHandle, new global::System.Collections.Generic.KeyValuePair("); + this.Write(this.ToStringHelper.ToStringWithCulture(item.Key)); + this.Write(", "); + this.Write(this.ToStringHelper.ToStringWithCulture(i)); + this.Write(") },\r\n"); + } + this.Write("\t\t\t};\r\n\t\t\tthis.keyToJumpMap = new global::System.Collections.Generic.Dictionary("); + this.Write(this.ToStringHelper.ToStringWithCulture(Info.SubTypes.Length)); + this.Write(")\r\n\t\t\t{\r\n"); + for(var i = 0; i < Info.SubTypes.Length; i++) { var item = Info.SubTypes[i]; + this.Write("\t\t\t\t{ "); + this.Write(this.ToStringHelper.ToStringWithCulture(item.Key)); + this.Write(", "); + this.Write(this.ToStringHelper.ToStringWithCulture(i)); + this.Write(" },\r\n"); + } + this.Write("\t\t\t};\r\n\t\t}\r\n\r\n\t\tpublic void Serialize(ref MsgPack::MessagePackWriter writer, "); + this.Write(this.ToStringHelper.ToStringWithCulture(Info.DataType.GetQualifiedName())); + this.Write(@" value, MsgPack::MessagePackSerializerOptions options) + { + global::System.Collections.Generic.KeyValuePair keyValuePair; + if (value != null && this.typeToKeyAndJumpMap.TryGetValue(value.GetType().TypeHandle, out keyValuePair)) + { + writer.WriteArrayHeader(2); + writer.WriteInt32(keyValuePair.Key); + switch (keyValuePair.Value) + { +"); + for(var i = 0; i < Info.SubTypes.Length; i++) { var item = Info.SubTypes[i]; + this.Write("\t\t\t\t\tcase "); + this.Write(this.ToStringHelper.ToStringWithCulture(i)); + this.Write(":\r\n\t\t\t\t\t\tMsgPack::FormatterResolverExtensions.GetFormatterWithVerify<"); + this.Write(this.ToStringHelper.ToStringWithCulture(item.Type)); + this.Write(">(options.Resolver).Serialize(ref writer, ("); + this.Write(this.ToStringHelper.ToStringWithCulture(item.Type)); + this.Write(")value, options);\r\n\t\t\t\t\t\tbreak;\r\n"); + } + this.Write("\t\t\t\t\tdefault:\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\twriter.WriteNil();\r" + + "\n\t\t}\r\n\r\n\t\tpublic "); + this.Write(this.ToStringHelper.ToStringWithCulture(Info.DataType.GetQualifiedName())); + this.Write(@" Deserialize(ref MsgPack::MessagePackReader reader, MsgPack::MessagePackSerializerOptions options) + { + if (reader.TryReadNil()) + { + return null; + } + + if (reader.ReadArrayHeader() != 2) + { + throw new global::System.InvalidOperationException(""Invalid Union data was detected. Type:"); + this.Write(this.ToStringHelper.ToStringWithCulture(Info.DataType.GetQualifiedName())); + this.Write("\");\r\n\t\t\t}\r\n\r\n\t\t\toptions.Security.DepthStep(ref reader);\r\n\t\t\tvar key = reader.Read" + + "Int32();\r\n\r\n\t\t\tif (!this.keyToJumpMap.TryGetValue(key, out key))\r\n\t\t\t{\r\n\t\t\t\tkey " + + "= -1;\r\n\t\t\t}\r\n\r\n\t\t\t"); + this.Write(this.ToStringHelper.ToStringWithCulture(Info.DataType.GetQualifiedName())); + this.Write(" result = null;\r\n\t\t\tswitch (key)\r\n\t\t\t{\r\n"); + for(var i = 0; i < Info.SubTypes.Length; i++) { var item = Info.SubTypes[i]; + this.Write("\t\t\t\tcase "); + this.Write(this.ToStringHelper.ToStringWithCulture(i)); + this.Write(":\r\n\t\t\t\t\tresult = ("); + this.Write(this.ToStringHelper.ToStringWithCulture(Info.DataType.GetQualifiedName())); + this.Write(")MsgPack::FormatterResolverExtensions.GetFormatterWithVerify<"); + this.Write(this.ToStringHelper.ToStringWithCulture(item.Type)); + this.Write(">(options.Resolver).Deserialize(ref reader, options);\r\n\t\t\t\t\tbreak;\r\n"); + } + this.Write("\t\t\t\tdefault:\r\n\t\t\t\t\treader.Skip();\r\n\t\t\t\t\tbreak;\r\n\t\t\t}\r\n\r\n\t\t\treader.Depth--;\r\n\t\t\tre" + + "turn result;\r\n\t\t}\r\n\t}\r\n\r\n"); + } + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] + public class UnionTemplateBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/src/MessagePack.SourceGenerator/Transforms/UnionTemplate.tt b/src/MessagePack.SourceGenerator/Transforms/UnionTemplate.tt new file mode 100644 index 000000000..67b78a537 --- /dev/null +++ b/src/MessagePack.SourceGenerator/Transforms/UnionTemplate.tt @@ -0,0 +1,93 @@ +<#@ template debug="false" hostspecific="false" linePragmas="false" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> + +using MsgPack = global::MessagePack; + +<# using (this.EmitNestingTypesAndNamespaces(this.Write)) { #> + internal sealed class <#= Info.Formatter.GetQualifiedName(Qualifiers.None) #>: MsgPack::Formatters.IMessagePackFormatter<<#= Info.DataType.GetQualifiedName() #>> + { + private readonly global::System.Collections.Generic.Dictionary> typeToKeyAndJumpMap; + private readonly global::System.Collections.Generic.Dictionary keyToJumpMap; + + public <#= Info.Formatter.Name #>() + { + this.typeToKeyAndJumpMap = new global::System.Collections.Generic.Dictionary>(<#= Info.SubTypes.Length #>, MsgPack::Internal.RuntimeTypeHandleEqualityComparer.Default) + { +<# for(var i = 0; i < Info.SubTypes.Length; i++) { var item = Info.SubTypes[i]; #> + { typeof(<#= item.Type #>).TypeHandle, new global::System.Collections.Generic.KeyValuePair(<#= item.Key #>, <#= i #>) }, +<# } #> + }; + this.keyToJumpMap = new global::System.Collections.Generic.Dictionary(<#= Info.SubTypes.Length #>) + { +<# for(var i = 0; i < Info.SubTypes.Length; i++) { var item = Info.SubTypes[i]; #> + { <#= item.Key #>, <#= i #> }, +<# } #> + }; + } + + public void Serialize(ref MsgPack::MessagePackWriter writer, <#= Info.DataType.GetQualifiedName() #> value, MsgPack::MessagePackSerializerOptions options) + { + global::System.Collections.Generic.KeyValuePair keyValuePair; + if (value != null && this.typeToKeyAndJumpMap.TryGetValue(value.GetType().TypeHandle, out keyValuePair)) + { + writer.WriteArrayHeader(2); + writer.WriteInt32(keyValuePair.Key); + switch (keyValuePair.Value) + { +<# for(var i = 0; i < Info.SubTypes.Length; i++) { var item = Info.SubTypes[i]; #> + case <#= i #>: + MsgPack::FormatterResolverExtensions.GetFormatterWithVerify<<#= item.Type #>>(options.Resolver).Serialize(ref writer, (<#= item.Type #>)value, options); + break; +<# } #> + default: + break; + } + + return; + } + + writer.WriteNil(); + } + + public <#= Info.DataType.GetQualifiedName() #> Deserialize(ref MsgPack::MessagePackReader reader, MsgPack::MessagePackSerializerOptions options) + { + if (reader.TryReadNil()) + { + return null; + } + + if (reader.ReadArrayHeader() != 2) + { + throw new global::System.InvalidOperationException("Invalid Union data was detected. Type:<#= Info.DataType.GetQualifiedName() #>"); + } + + options.Security.DepthStep(ref reader); + var key = reader.ReadInt32(); + + if (!this.keyToJumpMap.TryGetValue(key, out key)) + { + key = -1; + } + + <#= Info.DataType.GetQualifiedName() #> result = null; + switch (key) + { +<# for(var i = 0; i < Info.SubTypes.Length; i++) { var item = Info.SubTypes[i]; #> + case <#= i #>: + result = (<#= Info.DataType.GetQualifiedName() #>)MsgPack::FormatterResolverExtensions.GetFormatterWithVerify<<#= item.Type #>>(options.Resolver).Deserialize(ref reader, options); + break; +<# } #> + default: + reader.Skip(); + break; + } + + reader.Depth--; + return result; + } + } + +<# } #> diff --git a/src/MessagePack.SourceGenerator/Usings.cs b/src/MessagePack.SourceGenerator/Usings.cs new file mode 100644 index 000000000..8313513fc --- /dev/null +++ b/src/MessagePack.SourceGenerator/Usings.cs @@ -0,0 +1,5 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +global using MessagePack.SourceGenerator.Analyzers; +global using MessagePack.SourceGenerator.CodeAnalysis; diff --git a/src/MessagePack.SourceGenerator/Utils/AnalyzerUtilities.cs b/src/MessagePack.SourceGenerator/Utils/AnalyzerUtilities.cs new file mode 100644 index 000000000..d32584e2c --- /dev/null +++ b/src/MessagePack.SourceGenerator/Utils/AnalyzerUtilities.cs @@ -0,0 +1,193 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using static MessagePack.SourceGenerator.Constants; + +namespace MessagePack.SourceGenerator; + +public static class AnalyzerUtilities +{ + /// + /// Keep this list in sync with DynamicObjectTypeBuilder.IsOptimizeTargetType. + /// + public static readonly string[] PrimitiveTypes = + { + "short", + "int", + "long", + "ushort", + "uint", + "ulong", + "float", + "double", + "bool", + "byte", + "sbyte", + "char", + "byte[]", + + // Do not include types that resolvers are allowed to modify. + ////"global::System.DateTime", // OldSpec has no support, so for that and perf reasons a .NET native DateTime resolver exists. + ////"string", // https://github.com/Cysharp/MasterMemory provides custom formatter for string interning. + }; + + public static string? GetFullNamespaceName(this INamespaceSymbol? namespaceSymbol) + { + if (namespaceSymbol is null or { IsGlobalNamespace: true }) + { + return null; + } + + string? baseName = GetFullNamespaceName(namespaceSymbol.ContainingNamespace); + return baseName is null ? namespaceSymbol.Name : baseName + "." + namespaceSymbol.Name; + } + + public static string GetCanonicalTypeFullName(this ITypeSymbol typeSymbol) => typeSymbol.WithNullableAnnotation(NullableAnnotation.None).ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + + internal static string GetHelpLink(string diagnosticId) => $"https://github.com/MessagePack-CSharp/MessagePack-CSharp/blob/master/doc/analyzers/{diagnosticId}.md"; + + internal static AnalyzerOptions? ParseGeneratorAttribute(ImmutableArray attributes, ISymbol targetSymbol, CancellationToken cancellationToken) + { + AttributeData? generatorAttribute = attributes.SingleOrDefault(ad => + ad.AttributeClass?.Name == GeneratedMessagePackResolverAttributeName && + ad.AttributeClass?.ContainingNamespace.Name == AttributeNamespace); + + if (generatorAttribute is null) + { + return null; + } + + FormattersOptions formattersOptions = new() + { + UsesMapMode = generatorAttribute.GetSingleNamedArgumentValue("UseMapMode") is true, + }; + + ResolverOptions resolverOptions = new() + { + Name = new QualifiedNamedTypeName((INamedTypeSymbol)targetSymbol, ImmutableStack.Empty), + }; + + GeneratorOptions generatorOptions = new() + { + Formatters = formattersOptions, + Resolver = resolverOptions, + }; + + AnalyzerOptions options = new() + { + Generator = generatorOptions, + IsGeneratingSource = true, + }; + + return options; + } + + internal static ImmutableHashSet ParseKnownFormatterAttribute(ImmutableArray attributes, CancellationToken cancellationToken) + { + var builder = ImmutableHashSet.Empty.ToBuilder(); + foreach (AttributeData ad in attributes) + { + cancellationToken.ThrowIfCancellationRequested(); + if (ad.AttributeClass?.Name == MessagePackKnownFormatterAttributeName && ad.AttributeClass?.ContainingNamespace.Name == AttributeNamespace) + { + if (ad.ConstructorArguments[0].Value is INamedTypeSymbol formatterType && FormatterDescriptor.TryCreate(formatterType, out FormatterDescriptor? formatter)) + { + builder.Add(formatter); + } + } + } + + return builder.ToImmutable(); + } + + internal static ImmutableArray ParseAssumedFormattableAttribute(ImmutableArray attributes, CancellationToken cancellationToken) + { + return ImmutableArray.CreateRange( + from ad in attributes + where ad.AttributeClass?.Name == MessagePackAssumedFormattableAttributeName && ad.AttributeClass?.ContainingNamespace.Name == AttributeNamespace + let type = (INamedTypeSymbol?)ad.ConstructorArguments[0].Value + where type is not null + select new FormattableType(type, null)); + } + + internal static IEnumerable ResolverSymbolToInstanceExpression(SemanticModel semanticModel, IEnumerable formatterAndResolverTypes) + { + return formatterAndResolverTypes + .Where(r => r is not null && + (r.GetAttributes().Any(x => x.AttributeClass?.Name == GeneratedMessagePackResolverAttributeName && x.AttributeClass?.ContainingNamespace.GetFullNamespaceName() == AttributeNamespace) || + r.AllInterfaces.Any(x => x.Name == IFormatterResolverInterfaceName && x.ContainingNamespace.GetFullNamespaceName() == AttributeNamespace))) + .Select(r => + { + // Prefer to get the resolver by its static Instance property/field, if available. + if (r!.GetMembers("Instance").FirstOrDefault() is ISymbol { IsStatic: true } instanceMember) + { + if (instanceMember is IFieldSymbol or IPropertySymbol && semanticModel.IsAccessible(0, instanceMember)) + { + return $"{r.GetCanonicalTypeFullName()}.Instance"; + } + } + + // If the resolver has GeneratedMessagePackResolverAttribute, it has static Instance property + if (r.GetAttributes().Any(x => x.AttributeClass?.Name == GeneratedMessagePackResolverAttributeName && x.AttributeClass?.ContainingNamespace.GetFullNamespaceName() == AttributeNamespace)) + { + return $"{r.GetCanonicalTypeFullName()}.Instance"; + } + + // Fallback to instantiating the resolver, if a constructor is available. + if (r.InstanceConstructors.FirstOrDefault(c => c.Parameters.Length == 0) is IMethodSymbol ctor) + { + if (semanticModel.IsAccessible(0, ctor)) + { + return $"new {r.GetCanonicalTypeFullName()}()"; + } + } + + // No way to access an instance of the resolver. Produce something that will error out with direction for the user. + return $"#error No accessible default constructor or static Instance member on {r}."; + }); + } + + internal static IEnumerable FormatterSymbolToInstanceExpression(SemanticModel semanticModel, IEnumerable formatterAndResolverTypes) + { + return formatterAndResolverTypes + .Where(r => r is not null && r.AllInterfaces.Any(x => x.Name == IMessagePackFormatterInterfaceName && x.ContainingNamespace.GetFullNamespaceName() == IMessagePackFormatterInterfaceNamespace)) + .Select(r => + { + // Prefer to get the resolver by its static Instance property/field, if available. + if (r!.GetMembers("Instance").FirstOrDefault() is ISymbol { IsStatic: true } instanceMember) + { + if (instanceMember is IFieldSymbol or IPropertySymbol && semanticModel.IsAccessible(0, instanceMember)) + { + return $"{r.GetCanonicalTypeFullName()}.Instance"; + } + } + + // Fallback to instantiating the resolver, if a constructor is available. + if (r.InstanceConstructors.FirstOrDefault(c => c.Parameters.Length == 0) is IMethodSymbol ctor) + { + if (semanticModel.IsAccessible(0, ctor)) + { + return $"new {r.GetCanonicalTypeFullName()}()"; + } + } + + // No way to access an instance of the resolver. Produce something that will error out with direction for the user. + return $"#error No accessible default constructor or static Instance member on {r}."; + }); + } + + internal static IEnumerable SearchTypeForFormatterImplementations(INamedTypeSymbol symbol) + { + foreach (INamedTypeSymbol iface in symbol.AllInterfaces) + { + if (iface.IsGenericType && iface.TypeArguments.Length == 1 && + iface.Name == IMessagePackFormatterInterfaceName && iface.ContainingNamespace.GetFullNamespaceName() == IMessagePackFormatterInterfaceNamespace && + iface.TypeArguments[0] is INamedTypeSymbol formattedType) + { + yield return formattedType; + } + } + } +} diff --git a/src/MessagePack.SourceGenerator/Utils/Constants.cs b/src/MessagePack.SourceGenerator/Utils/Constants.cs new file mode 100644 index 000000000..4df875e4f --- /dev/null +++ b/src/MessagePack.SourceGenerator/Utils/Constants.cs @@ -0,0 +1,24 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace MessagePack.SourceGenerator; + +internal static class Constants +{ + internal const string AttributeNamespace = "MessagePack"; + internal const string CompositeResolverAttributeName = "CompositeResolverAttribute"; + internal const string CompositeResolverAttributeIncludeLocalFormattersPropertyName = "IncludeLocalFormatters"; + internal const string GeneratedMessagePackResolverAttributeName = "GeneratedMessagePackResolverAttribute"; + internal const string MessagePackKnownFormatterAttributeName = "MessagePackKnownFormatterAttribute"; + internal const string ExcludeFormatterFromSourceGeneratedResolverAttributeName = "ExcludeFormatterFromSourceGeneratedResolverAttribute"; + internal const string MessagePackAssumedFormattableAttributeName = "MessagePackAssumedFormattableAttribute"; + internal const string MessagePackObjectAttributeName = "MessagePackObjectAttribute"; + internal const string MessagePackUnionAttributeName = "UnionAttribute"; + internal const string SuppressSourceGenerationPropertyName = "SuppressSourceGeneration"; + internal const string AllowPrivatePropertyName = "AllowPrivate"; + + internal const string IMessagePackFormatterInterfaceNamespace = "MessagePack.Formatters"; + internal const string IMessagePackFormatterInterfaceName = "IMessagePackFormatter"; + + internal const string IFormatterResolverInterfaceName = "IFormatterResolver"; +} diff --git a/src/MessagePack.SourceGenerator/Utils/ImmutableEquatableArray.cs b/src/MessagePack.SourceGenerator/Utils/ImmutableEquatableArray.cs new file mode 100644 index 000000000..590524131 --- /dev/null +++ b/src/MessagePack.SourceGenerator/Utils/ImmutableEquatableArray.cs @@ -0,0 +1,96 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma warning disable SA1127 // Generic type constraints must be on own line +#pragma warning disable SA1402 // File may only contain a single type +#pragma warning disable SA1649 // File name should match first type name + +using System.Collections; + +namespace MessagePack.SourceGenerator; + +/// +/// Provides an immutable list implementation which implements sequence equality. +/// +public sealed class ImmutableEquatableArray : IEquatable>, IReadOnlyList + where T : IEquatable +{ + public static ImmutableEquatableArray Empty { get; } = new ImmutableEquatableArray(Array.Empty()); + + private readonly T[] values; + + public T this[int index] => values[index]; + + public int Count => values.Length; + + public ImmutableEquatableArray(IEnumerable values) + => this.values = values.ToArray(); + + public bool Equals(ImmutableEquatableArray? other) + => other is not null && ((ReadOnlySpan)values).SequenceEqual(other.values); + + public override bool Equals(object? obj) + => obj is ImmutableEquatableArray other && Equals(other); + + public override int GetHashCode() + { + int hash = 0; + foreach (T value in values) + { + hash = Combine(hash, value is null ? 0 : value.GetHashCode()); + } + + return hash; + + static int Combine(int h1, int h2) + { + uint rol5 = ((uint)h1 << 5) | ((uint)h1 >> 27); + return ((int)rol5 + h1) ^ h2; + } + } + + public Enumerator GetEnumerator() => new Enumerator(values); + + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)values).GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => values.GetEnumerator(); + + public struct Enumerator + { + private readonly T[] values; + private int index; + + internal Enumerator(T[] values) + { + this.values = values; + index = -1; + } + + public bool MoveNext() + { + int newIndex = index + 1; + + if ((uint)newIndex < (uint)values.Length) + { + index = newIndex; + return true; + } + + return false; + } + + public readonly T Current => values[index]; + } +} + +public static class ImmutableEquatableArray +{ + public static ImmutableEquatableArray Empty() where T : IEquatable + => ImmutableEquatableArray.Empty; + + public static ImmutableEquatableArray ToImmutableEquatableArray(this IEnumerable values) where T : IEquatable + => new(values); + + public static ImmutableEquatableArray Create(params T[] values) where T : IEquatable + => values is { Length: > 0 } ? new(values) : ImmutableEquatableArray.Empty; +} diff --git a/src/MessagePack.SourceGenerator/Utils/RoslynAnalyzerExtensions.cs b/src/MessagePack.SourceGenerator/Utils/RoslynAnalyzerExtensions.cs new file mode 100644 index 000000000..c43787f5d --- /dev/null +++ b/src/MessagePack.SourceGenerator/Utils/RoslynAnalyzerExtensions.cs @@ -0,0 +1,158 @@ +// Copyright (c) All contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace MessagePack.SourceGenerator; + +public static class RoslynAnalyzerExtensions +{ + public static bool ApproximatelyEqual(this INamedTypeSymbol? left, INamedTypeSymbol? right) + { + if (left is IErrorTypeSymbol || right is IErrorTypeSymbol) + { + return left?.ToDisplayString() == right?.ToDisplayString(); + } + else + { + return SymbolEqualityComparer.Default.Equals(left, right); + } + } + + public static EqualityMatch IsApproximatelyEqualOrDerivedFrom(this INamedTypeSymbol? left, INamedTypeSymbol? right) + { + if (left is IErrorTypeSymbol || right is IErrorTypeSymbol) + { + return left?.ToDisplayString() == right?.ToDisplayString() ? EqualityMatch.Equal : EqualityMatch.NotEqual; + } + else if (SymbolEqualityComparer.Default.Equals(left, right)) + { + return EqualityMatch.Equal; + } + else if (left is not null && right is not null && left.EnumerateBaseType().Any(bt => SymbolEqualityComparer.Default.Equals(bt, right))) + { + return EqualityMatch.LeftDerivesFromRight; + } + else + { + return EqualityMatch.NotEqual; + } + } + + public static IEnumerable EnumerateBaseType(this ITypeSymbol symbol) + { + INamedTypeSymbol? t = symbol.BaseType; + while (t != null) + { + yield return t; + t = t.BaseType; + } + } + + public static AttributeData? FindAttribute(this IEnumerable attributeDataList, string typeName) + { + return attributeDataList + .Where(x => x.AttributeClass?.ToDisplayString() == typeName) + .FirstOrDefault(); + } + + public static AttributeData? FindAttributeShortName(this IEnumerable attributeDataList, string typeName) + { + return attributeDataList + .Where(x => x.AttributeClass?.Name == typeName) + .FirstOrDefault(); + } + + public static AttributeData? FindAttributeIncludeBasePropertyShortName(this IPropertySymbol property, string typeName) + { + IPropertySymbol? loopingProperty = property; + do + { + AttributeData? data = FindAttributeShortName(loopingProperty.GetAttributes(), typeName); + if (data != null) + { + return data; + } + + loopingProperty = loopingProperty.OverriddenProperty; + } + while (loopingProperty != null); + + return null; + } + + public static AttributeSyntax? FindAttribute(this BaseTypeDeclarationSyntax typeDeclaration, SemanticModel model, string typeName) + { + return typeDeclaration.AttributeLists + .SelectMany(x => x.Attributes) + .Where(x => model.GetTypeInfo(x).Type?.ToDisplayString() == typeName) + .FirstOrDefault(); + } + + public static INamedTypeSymbol? FindBaseTargetType(this ITypeSymbol symbol, string typeName) + { + return symbol.EnumerateBaseType() + .Where(x => x.OriginalDefinition?.ToDisplayString() == typeName) + .FirstOrDefault(); + } + + public static object? GetSingleNamedArgumentValue(this AttributeData attribute, string key) + { + foreach (KeyValuePair item in attribute.NamedArguments) + { + if (item.Key == key) + { + return item.Value.Value; + } + } + + return null; + } + + public static bool IsNullable(this INamedTypeSymbol symbol) + { + if (symbol.IsGenericType) + { + if (symbol.ConstructUnboundGenericType().ToDisplayString() == "T?") + { + return true; + } + } + + return false; + } + + /// + /// Enumerates all members of the given type. + /// + /// The symbol to read members for. + /// The enumeration of members. Members are enumerated in the most derived type first, then each successive base type. + public static IEnumerable GetAllMembers(this ITypeSymbol symbol) + { + ITypeSymbol? t = symbol; + while (t != null) + { + foreach (ISymbol item in t.GetMembers()) + { + yield return item; + } + + t = t.BaseType; + } + } + + public static IEnumerable GetAllInterfaceMembers(this ITypeSymbol symbol) + { + return symbol.GetMembers() + .Concat(symbol.AllInterfaces.SelectMany(x => x.GetMembers())); + } + + public enum EqualityMatch + { + NotEqual, + Equal, + LeftDerivesFromRight, + } +} diff --git a/src/MessagePack.UnityClient/.config/dotnet-tools.json b/src/MessagePack.UnityClient/.config/dotnet-tools.json new file mode 100644 index 000000000..821e4ea8d --- /dev/null +++ b/src/MessagePack.UnityClient/.config/dotnet-tools.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "nugetforunity.cli": { + "version": "4.1.1", + "commands": [ + "nugetforunity" + ], + "rollForward": false + } + } +} \ No newline at end of file diff --git a/src/MessagePack.UnityClient/.vsconfig b/src/MessagePack.UnityClient/.vsconfig deleted file mode 100644 index d70cd98b7..000000000 --- a/src/MessagePack.UnityClient/.vsconfig +++ /dev/null @@ -1,6 +0,0 @@ -{ - "version": "1.0", - "components": [ - "Microsoft.VisualStudio.Workload.ManagedGame" - ] -} diff --git a/src/MessagePack.UnityClient/Assets/NuGet.config b/src/MessagePack.UnityClient/Assets/NuGet.config new file mode 100644 index 000000000..82fb1c875 --- /dev/null +++ b/src/MessagePack.UnityClient/Assets/NuGet.config @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/MessagePack.UnityClient/Assets/NuGet.config.meta b/src/MessagePack.UnityClient/Assets/NuGet.config.meta new file mode 100644 index 000000000..5325117d5 --- /dev/null +++ b/src/MessagePack.UnityClient/Assets/NuGet.config.meta @@ -0,0 +1,34 @@ +fileFormatVersion: 2 +guid: fe2fcfbf53515974890c7219634c8357 +labels: +- NuGetForUnity +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 1 + settings: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/MessagePack.UnityClient/Assets/Packages.meta b/src/MessagePack.UnityClient/Assets/Packages.meta new file mode 100644 index 000000000..6c6265d77 --- /dev/null +++ b/src/MessagePack.UnityClient/Assets/Packages.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f55a9c6bfe6b5ba41a8def05325b42f5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/MessagePack.UnityClient/Assets/Plugins.meta b/src/MessagePack.UnityClient/Assets/Plugins.meta deleted file mode 100644 index 7b05e62c2..000000000 --- a/src/MessagePack.UnityClient/Assets/Plugins.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 5fb5bfc38043b7444b650388190ea48f -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/src/MessagePack.UnityClient/Assets/RuntimeUnitTestToolkit.meta b/src/MessagePack.UnityClient/Assets/RuntimeUnitTestToolkit.meta deleted file mode 100644 index 85e74e6a0..000000000 --- a/src/MessagePack.UnityClient/Assets/RuntimeUnitTestToolkit.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: abcb1a88f6868eb4da993d61d6aeef51 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/src/MessagePack.UnityClient/Assets/RuntimeUnitTestToolkit/Editor.meta b/src/MessagePack.UnityClient/Assets/RuntimeUnitTestToolkit/Editor.meta deleted file mode 100644 index 2e4c0c54e..000000000 --- a/src/MessagePack.UnityClient/Assets/RuntimeUnitTestToolkit/Editor.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: bc6ad6aaa548c204f9866a24b01d4507 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/src/MessagePack.UnityClient/Assets/RuntimeUnitTestToolkit/Editor/HierarchyTreeBuilder.cs b/src/MessagePack.UnityClient/Assets/RuntimeUnitTestToolkit/Editor/HierarchyTreeBuilder.cs deleted file mode 100644 index 9a2adc2b5..000000000 --- a/src/MessagePack.UnityClient/Assets/RuntimeUnitTestToolkit/Editor/HierarchyTreeBuilder.cs +++ /dev/null @@ -1,133 +0,0 @@ -#if UNITY_EDITOR - -using System; -using UnityEngine; - -namespace RuntimeUnitTestToolkit.Editor -{ - // functional declarative construction like flutter. - - internal interface IBuilder - { - GameObject GameObject { get; } - T GetComponent(); - } - - internal class Builder : IBuilder - where T : Component - { - public T Component1 { get; private set; } - public GameObject GameObject { get; private set; } - - public Transform Transform { get { return GameObject.transform; } } - public RectTransform RectTransform { get { return GameObject.GetComponent(); } } - - public Action SetTarget - { - set - { - value(this.GameObject); - } - } - - - public IBuilder Child - { - set - { - value.GameObject.transform.SetParent(GameObject.transform); - } - } - - public IBuilder[] Children - { - set - { - foreach (var item in value) - { - item.GameObject.transform.SetParent(GameObject.transform); - } - } - } - - public Builder(string name) - { - this.GameObject = new GameObject(name); - this.Component1 = GameObject.AddComponent(); - } - - public Builder(string name, out T referenceSelf) // out primary reference. - { - this.GameObject = new GameObject(name); - this.Component1 = GameObject.AddComponent(); - referenceSelf = this.Component1; - } - - public TComponent GetComponent() - { - return this.GameObject.GetComponent(); - } - } - - internal class Builder : Builder - where T1 : Component - where T2 : Component - { - public T2 Component2 { get; private set; } - - public Builder(string name) - : base(name) - { - this.Component2 = GameObject.AddComponent(); - } - - public Builder(string name, out T1 referenceSelf) - : base(name, out referenceSelf) - { - this.Component2 = GameObject.AddComponent(); - } - } - - internal class Builder : Builder - where T1 : Component - where T2 : Component - where T3 : Component - { - public T3 Component3 { get; private set; } - - public Builder(string name) - : base(name) - { - this.Component3 = GameObject.AddComponent(); - } - - public Builder(string name, out T1 referenceSelf) - : base(name, out referenceSelf) - { - this.Component3 = GameObject.AddComponent(); - } - } - - internal class Builder : Builder - where T1 : Component - where T2 : Component - where T3 : Component - where T4 : Component - { - public T4 Component4 { get; private set; } - - public Builder(string name) - : base(name) - { - this.Component4 = GameObject.AddComponent(); - } - - public Builder(string name, out T1 referenceSelf) - : base(name, out referenceSelf) - { - this.Component4 = GameObject.AddComponent(); - } - } -} - -#endif \ No newline at end of file diff --git a/src/MessagePack.UnityClient/Assets/RuntimeUnitTestToolkit/Editor/HierarchyTreeBuilder.cs.meta b/src/MessagePack.UnityClient/Assets/RuntimeUnitTestToolkit/Editor/HierarchyTreeBuilder.cs.meta deleted file mode 100644 index 82c8e9bb4..000000000 --- a/src/MessagePack.UnityClient/Assets/RuntimeUnitTestToolkit/Editor/HierarchyTreeBuilder.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 8760bbbab905a534eb6fb7b61b736926 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/src/MessagePack.UnityClient/Assets/RuntimeUnitTestToolkit/Editor/UnitTestBuilder.MenuItems.cs b/src/MessagePack.UnityClient/Assets/RuntimeUnitTestToolkit/Editor/UnitTestBuilder.MenuItems.cs deleted file mode 100644 index 94508b51c..000000000 --- a/src/MessagePack.UnityClient/Assets/RuntimeUnitTestToolkit/Editor/UnitTestBuilder.MenuItems.cs +++ /dev/null @@ -1,345 +0,0 @@ -#if UNITY_EDITOR -using UnityEditor; - -// Settings MenuItems. - -public static partial class UnitTestBuilder -{ - [MenuItem("Test/Settings/ScriptBackend/Mono", validate = true, priority = 1)] - static bool ValidateScriptBackendMono() - { - Menu.SetChecked("Test/Settings/ScriptBackend/Mono", LoadOrGetDefaultSettings().ScriptBackend == ScriptingImplementation.Mono2x); - return true; - } - - [MenuItem("Test/Settings/ScriptBackend/Mono", validate = false, priority = 1)] - static void ScriptBackendMono() - { - var settings = LoadOrGetDefaultSettings(); - settings.UseCurrentScriptBackend = false; - settings.ScriptBackend = ScriptingImplementation.Mono2x; - SaveSettings(settings); - } - - [MenuItem("Test/Settings/ScriptBackend/IL2CPP", validate = true, priority = 2)] - static bool ValidateScriptBackendIL2CPP() - { - Menu.SetChecked("Test/Settings/ScriptBackend/IL2CPP", LoadOrGetDefaultSettings().ScriptBackend == ScriptingImplementation.IL2CPP); - return true; - } - - [MenuItem("Test/Settings/ScriptBackend/IL2CPP", validate = false, priority = 2)] - static void ScriptBackendIL2CPP() - { - var settings = LoadOrGetDefaultSettings(); - settings.UseCurrentScriptBackend = false; - settings.ScriptBackend = ScriptingImplementation.IL2CPP; - SaveSettings(settings); - } - - [MenuItem("Test/Settings/AutoRunPlayer", validate = true, priority = 3)] - static bool ValidateAutoRun() - { - Menu.SetChecked("Test/Settings/AutoRunPlayer", LoadOrGetDefaultSettings().AutoRunPlayer); - return true; - } - - [MenuItem("Test/Settings/AutoRunPlayer", validate = false, priority = 3)] - static void AutoRun() - { - var settings = LoadOrGetDefaultSettings(); - settings.AutoRunPlayer = !settings.AutoRunPlayer; - SaveSettings(settings); - } - - [MenuItem("Test/Settings/Headless", validate = true, priority = 4)] - static bool ValidateHeadless() - { - Menu.SetChecked("Test/Settings/Headless", LoadOrGetDefaultSettings().Headless); - return true; - } - - [MenuItem("Test/Settings/Headless", validate = false, priority = 4)] - static void Headless() - { - var settings = LoadOrGetDefaultSettings(); - settings.Headless = !settings.Headless; - SaveSettings(settings); - } - - [MenuItem("Test/Settings/DisableAutoClose", validate = true, priority = 5)] - static bool ValidateDisableAutoClose() - { - Menu.SetChecked("Test/Settings/DisableAutoClose", LoadOrGetDefaultSettings().DisableAutoClose); - return true; - } - - [MenuItem("Test/Settings/DisableAutoClose", validate = false, priority = 5)] - static void DisableAutoClose() - { - var settings = LoadOrGetDefaultSettings(); - settings.DisableAutoClose = !settings.DisableAutoClose; - SaveSettings(settings); - } - - // generated - - /* - * - void Main() -{ -var sb = new StringBuilder(); - -var p = 1; -foreach (var target in Enum.GetNames(typeof(BuildTarget))) -{ - var path = $"Test/Settings/BuildTarget/{target}"; - var priority = p++; - - var template = $@" -[MenuItem(""{path}"", validate = true, priority = {priority})] -static bool ValidateBuildTarget{target}() -{{ -Menu.SetChecked(""{path}"", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.{target}); -return true; -}} - -[MenuItem(""{path}"", validate = false, priority = {priority})] -static void BuildTarget{target}() -{{ -var settings = LoadOrGetDefaultSettings(); -settings.UseCurrentBuildTarget = false; -settings.BuildTarget = BuildTarget.{target}; -SaveSettings(settings); -}}"; - - sb.AppendLine(template); -} - -sb.ToString().Dump(); -} - -public enum BuildTarget -{ -StandaloneWindows, -StandaloneWindows64, -StandaloneLinux, -StandaloneLinux64, -StandaloneOSX, -WebGL, -iOS, -Android, -WSAPlayer, -PS4, -XboxOne, -Switch, -} - */ - - - [MenuItem("Test/Settings/BuildTarget/StandaloneWindows", validate = true, priority = 1)] - static bool ValidateBuildTargetStandaloneWindows() - { - Menu.SetChecked("Test/Settings/BuildTarget/StandaloneWindows", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.StandaloneWindows); - return true; - } - - [MenuItem("Test/Settings/BuildTarget/StandaloneWindows", validate = false, priority = 1)] - static void BuildTargetStandaloneWindows() - { - var settings = LoadOrGetDefaultSettings(); - settings.UseCurrentBuildTarget = false; - settings.BuildTarget = BuildTarget.StandaloneWindows; - SaveSettings(settings); - } - - [MenuItem("Test/Settings/BuildTarget/StandaloneWindows64", validate = true, priority = 2)] - static bool ValidateBuildTargetStandaloneWindows64() - { - Menu.SetChecked("Test/Settings/BuildTarget/StandaloneWindows64", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.StandaloneWindows64); - return true; - } - - [MenuItem("Test/Settings/BuildTarget/StandaloneWindows64", validate = false, priority = 2)] - static void BuildTargetStandaloneWindows64() - { - var settings = LoadOrGetDefaultSettings(); - settings.UseCurrentBuildTarget = false; - settings.BuildTarget = BuildTarget.StandaloneWindows64; - SaveSettings(settings); - } - -#if !UNITY_2019_2_OR_NEWER - - [MenuItem("Test/Settings/BuildTarget/StandaloneLinux", validate = true, priority = 3)] - static bool ValidateBuildTargetStandaloneLinux() - { - Menu.SetChecked("Test/Settings/BuildTarget/StandaloneLinux", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.StandaloneLinux); - return true; - } - - [MenuItem("Test/Settings/BuildTarget/StandaloneLinux", validate = false, priority = 3)] - static void BuildTargetStandaloneLinux() - { - var settings = LoadOrGetDefaultSettings(); - settings.UseCurrentBuildTarget = false; - settings.BuildTarget = BuildTarget.StandaloneLinux; - SaveSettings(settings); - } - -#endif - - [MenuItem("Test/Settings/BuildTarget/StandaloneLinux64", validate = true, priority = 4)] - static bool ValidateBuildTargetStandaloneLinux64() - { - Menu.SetChecked("Test/Settings/BuildTarget/StandaloneLinux64", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.StandaloneLinux64); - return true; - } - - [MenuItem("Test/Settings/BuildTarget/StandaloneLinux64", validate = false, priority = 4)] - static void BuildTargetStandaloneLinux64() - { - var settings = LoadOrGetDefaultSettings(); - settings.UseCurrentBuildTarget = false; - settings.BuildTarget = BuildTarget.StandaloneLinux64; - SaveSettings(settings); - } - - [MenuItem("Test/Settings/BuildTarget/StandaloneOSX", validate = true, priority = 5)] - static bool ValidateBuildTargetStandaloneOSX() - { - Menu.SetChecked("Test/Settings/BuildTarget/StandaloneOSX", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.StandaloneOSX); - return true; - } - - [MenuItem("Test/Settings/BuildTarget/StandaloneOSX", validate = false, priority = 5)] - static void BuildTargetStandaloneOSX() - { - var settings = LoadOrGetDefaultSettings(); - settings.UseCurrentBuildTarget = false; - settings.BuildTarget = BuildTarget.StandaloneOSX; - SaveSettings(settings); - } - - [MenuItem("Test/Settings/BuildTarget/WebGL", validate = true, priority = 6)] - static bool ValidateBuildTargetWebGL() - { - Menu.SetChecked("Test/Settings/BuildTarget/WebGL", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.WebGL); - return true; - } - - [MenuItem("Test/Settings/BuildTarget/WebGL", validate = false, priority = 6)] - static void BuildTargetWebGL() - { - var settings = LoadOrGetDefaultSettings(); - settings.UseCurrentBuildTarget = false; - settings.BuildTarget = BuildTarget.WebGL; - SaveSettings(settings); - } - - [MenuItem("Test/Settings/BuildTarget/iOS", validate = true, priority = 7)] - static bool ValidateBuildTargetiOS() - { - Menu.SetChecked("Test/Settings/BuildTarget/iOS", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.iOS); - return true; - } - - [MenuItem("Test/Settings/BuildTarget/iOS", validate = false, priority = 7)] - static void BuildTargetiOS() - { - var settings = LoadOrGetDefaultSettings(); - settings.UseCurrentBuildTarget = false; - settings.BuildTarget = BuildTarget.iOS; - SaveSettings(settings); - } - - [MenuItem("Test/Settings/BuildTarget/Android", validate = true, priority = 8)] - static bool ValidateBuildTargetAndroid() - { - Menu.SetChecked("Test/Settings/BuildTarget/Android", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.Android); - return true; - } - - [MenuItem("Test/Settings/BuildTarget/Android", validate = false, priority = 8)] - static void BuildTargetAndroid() - { - var settings = LoadOrGetDefaultSettings(); - settings.UseCurrentBuildTarget = false; - settings.BuildTarget = BuildTarget.Android; - SaveSettings(settings); - } - - [MenuItem("Test/Settings/BuildTarget/WSAPlayer", validate = true, priority = 9)] - static bool ValidateBuildTargetWSAPlayer() - { - Menu.SetChecked("Test/Settings/BuildTarget/WSAPlayer", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.WSAPlayer); - return true; - } - - [MenuItem("Test/Settings/BuildTarget/WSAPlayer", validate = false, priority = 9)] - static void BuildTargetWSAPlayer() - { - var settings = LoadOrGetDefaultSettings(); - settings.UseCurrentBuildTarget = false; - settings.BuildTarget = BuildTarget.WSAPlayer; - SaveSettings(settings); - } - - [MenuItem("Test/Settings/BuildTarget/PS4", validate = true, priority = 10)] - static bool ValidateBuildTargetPS4() - { - Menu.SetChecked("Test/Settings/BuildTarget/PS4", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.PS4); - return true; - } - - [MenuItem("Test/Settings/BuildTarget/PS4", validate = false, priority = 10)] - static void BuildTargetPS4() - { - var settings = LoadOrGetDefaultSettings(); - settings.UseCurrentBuildTarget = false; - settings.BuildTarget = BuildTarget.PS4; - SaveSettings(settings); - } - - [MenuItem("Test/Settings/BuildTarget/XboxOne", validate = true, priority = 11)] - static bool ValidateBuildTargetXboxOne() - { - Menu.SetChecked("Test/Settings/BuildTarget/XboxOne", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.XboxOne); - return true; - } - - [MenuItem("Test/Settings/BuildTarget/XboxOne", validate = false, priority = 11)] - static void BuildTargetXboxOne() - { - var settings = LoadOrGetDefaultSettings(); - settings.UseCurrentBuildTarget = false; - settings.BuildTarget = BuildTarget.XboxOne; - SaveSettings(settings); - } - - [MenuItem("Test/Settings/BuildTarget/Switch", validate = true, priority = 12)] - static bool ValidateBuildTargetSwitch() - { - Menu.SetChecked("Test/Settings/BuildTarget/Switch", LoadOrGetDefaultSettings().BuildTarget == BuildTarget.Switch); - return true; - } - - [MenuItem("Test/Settings/BuildTarget/Switch", validate = false, priority = 12)] - static void BuildTargetSwitch() - { - var settings = LoadOrGetDefaultSettings(); - settings.UseCurrentBuildTarget = false; - settings.BuildTarget = BuildTarget.Switch; - SaveSettings(settings); - } - - - - - - - - -} - -#endif diff --git a/src/MessagePack.UnityClient/Assets/RuntimeUnitTestToolkit/Editor/UnitTestBuilder.MenuItems.cs.meta b/src/MessagePack.UnityClient/Assets/RuntimeUnitTestToolkit/Editor/UnitTestBuilder.MenuItems.cs.meta deleted file mode 100644 index 7c9917290..000000000 --- a/src/MessagePack.UnityClient/Assets/RuntimeUnitTestToolkit/Editor/UnitTestBuilder.MenuItems.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 12bdad0556e999f4aa82da29415d361f -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/src/MessagePack.UnityClient/Assets/RuntimeUnitTestToolkit/Editor/UnitTestBuilder.cs b/src/MessagePack.UnityClient/Assets/RuntimeUnitTestToolkit/Editor/UnitTestBuilder.cs deleted file mode 100644 index 9d7ddeb16..000000000 --- a/src/MessagePack.UnityClient/Assets/RuntimeUnitTestToolkit/Editor/UnitTestBuilder.cs +++ /dev/null @@ -1,552 +0,0 @@ -#if UNITY_EDITOR - -using RuntimeUnitTestToolkit; -using RuntimeUnitTestToolkit.Editor; -using System; -using UnityEditor; -using UnityEditor.Build; -using UnityEditor.Build.Reporting; -using UnityEditor.SceneManagement; -using UnityEngine; -using UnityEngine.EventSystems; -using UnityEngine.SceneManagement; -using UnityEngine.UI; - -internal class RuntimeUnitTestSettings -{ - public ScriptingImplementation ScriptBackend; - public bool UseCurrentScriptBackend; - public BuildTarget BuildTarget; - public bool UseCurrentBuildTarget; - - public bool Headless; - public bool AutoRunPlayer; - public bool DisableAutoClose; - - public RuntimeUnitTestSettings() - { - UseCurrentBuildTarget = true; - UseCurrentScriptBackend = true; - Headless = false; - AutoRunPlayer = true; - DisableAutoClose = false; - } - - public override string ToString() - { - return $"{ScriptBackend} {BuildTarget} Headless:{Headless} AutoRunPlayer:{AutoRunPlayer} DisableAutoClose:{DisableAutoClose}"; - } -} - -// no namespace(because invoke from commandline) -public static partial class UnitTestBuilder -{ - const string SettingsKeyBase = "RuntimeUnitTest.Settings."; - - [MenuItem("Test/BuildUnitTest")] - public static void BuildUnitTest() - { - var settings = new RuntimeUnitTestSettings(); // default - - string buildPath = null; - - if (Application.isBatchMode) // from commandline - { - settings.AutoRunPlayer = false; - settings.DisableAutoClose = false; - - var cmdArgs = Environment.GetCommandLineArgs(); - for (int i = 0; i < cmdArgs.Length; i++) - { - if (string.Equals(cmdArgs[i].Trim('-', '/'), "ScriptBackend", StringComparison.OrdinalIgnoreCase)) - { - settings.UseCurrentScriptBackend = false; - var str = cmdArgs[++i]; - if (str.StartsWith("mono", StringComparison.OrdinalIgnoreCase)) - { - settings.ScriptBackend = ScriptingImplementation.Mono2x; - } - else if (str.StartsWith("IL2CPP", StringComparison.OrdinalIgnoreCase)) - { - settings.ScriptBackend = ScriptingImplementation.IL2CPP; - } - else - { - settings.ScriptBackend = (ScriptingImplementation)Enum.Parse(typeof(ScriptingImplementation), str, true); - } - } - else if (string.Equals(cmdArgs[i].Trim('-', '/'), "BuildTarget", StringComparison.OrdinalIgnoreCase)) - { - settings.UseCurrentBuildTarget = false; - settings.BuildTarget = (BuildTarget)Enum.Parse(typeof(BuildTarget), cmdArgs[++i], true); - } - else if (string.Equals(cmdArgs[i].Trim('-', '/'), "Headless", StringComparison.OrdinalIgnoreCase)) - { - settings.Headless = true; - } - else if (string.Equals(cmdArgs[i].Trim('-', '/'), "buildPath", StringComparison.OrdinalIgnoreCase)) - { - buildPath = cmdArgs[++i]; - } - } - } - else - { - var key = SettingsKeyBase + Application.productName; - var settingsValue = EditorPrefs.GetString(key, null); - try - { - if (!string.IsNullOrWhiteSpace(settingsValue)) - { - settings = JsonUtility.FromJson(settingsValue); - } - } - catch - { - UnityEngine.Debug.LogError("Fail to load RuntimeUnitTest settings"); - EditorPrefs.SetString(key, null); - } - } - - if (settings.UseCurrentBuildTarget) - { - settings.BuildTarget = EditorUserBuildSettings.activeBuildTarget; - } - if (settings.UseCurrentScriptBackend) - { - settings.ScriptBackend = PlayerSettings.GetScriptingBackend(ToBuildTargetGroup(settings.BuildTarget)); - } - - if (buildPath == null) - { - buildPath = $"bin/UnitTest/{settings.BuildTarget}_{settings.ScriptBackend}/test" + GetExtensionForBuildTarget(settings.BuildTarget); - } - - var originalScene = SceneManager.GetActiveScene().path; - - BuildUnitTest(buildPath, settings.ScriptBackend, settings.BuildTarget, settings.Headless, settings.AutoRunPlayer, settings.DisableAutoClose); - - // reopen original scene - if (!string.IsNullOrWhiteSpace(originalScene)) - { - EditorSceneManager.OpenScene(originalScene, OpenSceneMode.Single); - } - else - { - EditorSceneManager.NewScene(NewSceneSetup.DefaultGameObjects); - } - } - - - [MenuItem("Test/LoadUnitTestScene")] - public static void LoadUnitTestScene() - { - var scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single); - BuildUnitTestRunnerScene(); - EditorSceneManager.MarkSceneDirty(scene); - } - - static RuntimeUnitTestSettings LoadOrGetDefaultSettings() - { - var key = SettingsKeyBase + Application.productName; - - var settingsValue = EditorPrefs.GetString(key, null); - RuntimeUnitTestSettings settings = null; - try - { - if (!string.IsNullOrWhiteSpace(settingsValue)) - { - settings = JsonUtility.FromJson(settingsValue); - } - } - catch - { - UnityEngine.Debug.LogError("Fail to load RuntimeUnitTest settings"); - EditorPrefs.SetString(key, null); - settings = null; - } - - if (settings == null) - { - // default - settings = new RuntimeUnitTestSettings - { - UseCurrentBuildTarget = true, - UseCurrentScriptBackend = true, - Headless = false, - AutoRunPlayer = true, - }; - } - - return settings; - } - - static void SaveSettings(RuntimeUnitTestSettings settings) - { - var key = SettingsKeyBase + Application.productName; - EditorPrefs.SetString(key, JsonUtility.ToJson(settings)); - } - - public static void BuildUnitTest(string buildPath, ScriptingImplementation scriptBackend, BuildTarget buildTarget, bool headless, bool autoRunPlayer, bool disableAutoClose) - { - var sceneName = "Assets/TempRuntimeUnitTestScene_" + DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - if (disableAutoClose) - { - sceneName += "_DisableAutoClose"; - } - sceneName += ".unity"; - - var scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single); - - BuildUnitTestRunnerScene(); - - EditorSceneManager.MarkSceneDirty(scene); - AssetDatabase.SaveAssets(); - EditorSceneManager.SaveScene(scene, sceneName, false); - try - { - Build(sceneName, buildPath, new RuntimeUnitTestSettings { ScriptBackend = scriptBackend, BuildTarget = buildTarget, Headless = headless, AutoRunPlayer = autoRunPlayer, DisableAutoClose = disableAutoClose }); - } - finally - { - AssetDatabase.DeleteAsset(sceneName); - } - } - - public static UnitTestRunner BuildUnitTestRunnerScene() - { - const string kStandardSpritePath = "UI/Skin/UISprite.psd"; - const string kBackgroundSpritePath = "UI/Skin/Background.psd"; - var uisprite = AssetDatabase.GetBuiltinExtraResource(kStandardSpritePath); - var background = AssetDatabase.GetBuiltinExtraResource(kBackgroundSpritePath); - - ScrollRect buttonList; - VerticalLayoutGroup listLayout; - Scrollbar refListScrollbar; - ScrollRect logList; - Scrollbar refLogScrollbar; - Button clearButton; - Text logText; - - // Flutter like coded build utility - - var rootObject = new Builder("SceneRoot") - { - Children = new IBuilder[] { - new Builder("EventSystem"), - new Builder("Canvas") { - Component1 = { renderMode = RenderMode.ScreenSpaceOverlay }, - Children = new IBuilder[] { - new Builder("HorizontalSplitter") { - RectTransform = { anchorMin = new Vector2(0, 0), anchorMax = new Vector2(1, 1) }, - Component1 = { childControlWidth = true, childControlHeight = true, spacing = 10 }, - Children = new IBuilder[] { - new Builder("ButtonList", out buttonList) { - RectTransform = { pivot = new Vector2(0.5f, 0.5f) }, - Component1 = { horizontal =false, vertical = true, movementType = ScrollRect.MovementType.Clamped }, - Children = new IBuilder[] { - new Builder("ListLayoutToAttach", out listLayout) { - RectTransform = { anchorMin = new Vector2(0, 0), anchorMax = new Vector2(1, 1), pivot = new Vector2(0, 1) }, - Component1 = { childControlWidth = true, childControlHeight = true, childForceExpandWidth = true, childForceExpandHeight = false, spacing = 10, padding = new RectOffset(10,20,10,10) }, - Component2 = { horizontalFit = ContentSizeFitter.FitMode.Unconstrained, verticalFit = ContentSizeFitter.FitMode.PreferredSize }, - SetTarget = self => { buttonList.content = self.GetComponent(); }, - Child = new Builder("ClearButton", out clearButton) { - Component2 = { sprite = uisprite, type = Image.Type.Sliced }, - Component3 = { minHeight = 50 }, - SetTarget = self => { self.GetComponent