diff --git a/.github/skills/fsharp-diagnostics/server/Directory.Build.props b/.github/skills/fsharp-diagnostics/server/Directory.Build.props
index 5a08e96c89f..48e48f88427 100644
--- a/.github/skills/fsharp-diagnostics/server/Directory.Build.props
+++ b/.github/skills/fsharp-diagnostics/server/Directory.Build.props
@@ -3,6 +3,8 @@
Also blocks Directory.Build.targets import. -->
false
+
+ false$(MSBuildThisFileDirectory)../../../../.tools/fsharp-diag/bin/$(MSBuildThisFileDirectory)../../../../.tools/fsharp-diag/obj/
diff --git a/.github/skills/pr-description/SKILL.md b/.github/skills/pr-description/SKILL.md
index 9cd0918015e..41b7833a45b 100644
--- a/.github/skills/pr-description/SKILL.md
+++ b/.github/skills/pr-description/SKILL.md
@@ -9,13 +9,14 @@ Reviewers can already see the Files tab, the commit log, and the issue thread. S
## Rules
-Rules 1, 2, 4, 5 are defaults; if the user insists, push back once then comply. Rule 3 is non-negotiable — `-b "..."` ships broken markdown (see PR #19866).
+Rules 1, 2, 4, 5, 6 are defaults; if the user insists, push back once then comply. Rule 3 is non-negotiable — `-b "..."` ships broken markdown (see PR #19866).
1. **No change inventory.** No file/module/method/test lists. No `## Changes`/`## Implementation` section. Mention an identifier only when it *is* the user-visible behavior. Whatever the reader already has (Files tab for PRs, commit log for follow-up comments, issue history for issue edits) — don't re-list it.
2. **No LLM slop, no justification scaffolding.** No emoji headers, no "TL;DR" above a 3-line body, no Motivation/Background/Approach/Testing sections, no re-stating the title or the comment you're replying to. No "matching the X norm", no "preventing the Y failure (PR #ZZZZ)", no stats, no links to past PRs as proof. The diff is the proof.
3. **Body via `--body-file`, built without shell expansion.** Write the file with your file-creation/edit tool (it writes bytes verbatim — no `$`/backtick evaluation, no delimiter collisions, OS-agnostic). Never `-b "..."` / `--body "..."` — backticks and `$` get shell-evaluated and the render breaks. If you build the file in a shell, use a pwsh verbatim here-string `@'...'@` (cross-platform; single-quoted is mandatory). Applies to `gh pr create/edit/comment/review`, `gh issue create/edit/comment`.
4. **`Fixes #N` to close issues.** Use only when the PR actually closes #N (auto-closes on merge). It is the highest-value line in most PR bodies — never omit it when valid. No "Related to" / speculative links. Preserve existing trailers (`Co-authored-by:`, `Signed-off-by:`, `Reverts #N`); don't invent them.
5. **Title:** imperative, ≤72 chars, no trailing period, no `fix:`/`feat:` prefix. Name the behavior, not the file. A specific title lets the body shrink to `Fixes #N` + one sentence.
+6. **No hard-wrapped prose.** Write each paragraph as one unbroken line and let GitHub's renderer wrap it — blank lines separate paragraphs, and that's the only break you author. Manual mid-sentence line breaks (wrapping at a fixed column) are a machine tell and render raggedly across window widths.
## PR-body shapes (pick the smallest that carries the signal)
@@ -28,16 +29,14 @@ Update .NET SDK from 10.0.202 to 10.0.204.
~~~
Fixes #18009
-Wrong colorization when a qualified type name with generic parameters
-is used in a static member access expression.
+Wrong colorization when a qualified type name with generic parameters is used in a static member access expression.
~~~
**Issue link + 1-sentence why** — the most common non-trivial shape:
~~~
Fixes #19751
-`--refout` MVIDs were unstable because hashing relied on per-process
-string randomization. Switched to a deterministic hash.
+`--refout` MVIDs were unstable because hashing relied on per-process string randomization. Switched to a deterministic hash.
~~~
**Before/After code block** — when prose loses information; ≤15 lines, language tag:
@@ -73,8 +72,7 @@ Show the title + body (or comment text) in chat first. **Do not run `gh` until t
```powershell
@'
- Fix false-positive FS3261 when nullness narrowing leaks across iterations
- of seq/list/array comprehensions.
+ Fix false-positive FS3261 when nullness narrowing leaks across iterations of seq/list/array comprehensions.
Fixes #19644
'@ | Set-Content -NoNewline pr-body.md
diff --git a/.github/workflows/check_release_notes.yml b/.github/workflows/check_release_notes.yml
index 1681a57f399..bed91b1b52d 100644
--- a/.github/workflows/check_release_notes.yml
+++ b/.github/workflows/check_release_notes.yml
@@ -6,53 +6,52 @@ on:
- 'main'
- 'release/*'
permissions:
+ contents: read
issues: write
pull-requests: write
+concurrency:
+ group: release-notes-${{ github.event.pull_request.number }}
+ cancel-in-progress: true
jobs:
check_release_notes:
permissions:
- issues: write
- pull-requests: write
+ contents: read
+ issues: write
+ pull-requests: write
env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GH_TOKEN: ${{ github.token }}
+ PR_AUTHOR: ${{ github.event.pull_request.user.login }}
+ PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
+ PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
+ PR_LABELS: ${{ toJSON(github.event.pull_request.labels) }}
+ PR_NUMBER: ${{ github.event.pull_request.number }}
+ OPT_OUT_RELEASE_NOTES: ${{ contains(github.event.pull_request.labels.*.name, 'NO_RELEASE_NOTES') }}
+ VNEXT: ${{ vars.VNEXT }}
runs-on: ubuntu-latest
steps:
- - name: Get github ref
- uses: actions/github-script@v3
- id: get-pr
- with:
- script: |
- const result = await github.pulls.get({
- pull_number: context.issue.number,
- owner: context.repo.owner,
- repo: context.repo.repo,
- });
- return { "pr_number": context.issue.number, "ref": result.data.head.ref, "repository": result.data.head.repo.full_name};
- - name: Checkout repo
- uses: actions/checkout@v2
- with:
- repository: ${{ fromJson(steps.get-pr.outputs.result).repository }}
- ref: ${{ fromJson(steps.get-pr.outputs.result).ref }}
- fetch-depth: 0
- name: Check for release notes changes
id: release_notes_changes
run: |
- set -e
+ set -euo pipefail
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
FSHARP_REPO_URL="https://github.com/${GITHUB_REPOSITORY}"
- PR_AUTHOR="${{ github.event.pull_request.user.login }}"
- PR_NUMBER=${{ github.event.number }}
PR_URL="${FSHARP_REPO_URL}/pull/${PR_NUMBER}"
- echo "PR Tags: ${{ toJson(github.event.pull_request.labels) }}"
-
- OPT_OUT_RELEASE_NOTES=${{ contains(github.event.pull_request.labels.*.name, 'NO_RELEASE_NOTES') }}
+ [[ "$PR_BASE_SHA" =~ ^[0-9a-f]{40}$ ]] || { echo "::error::Unexpected base SHA: $PR_BASE_SHA"; exit 1; }
+ [[ "$PR_HEAD_SHA" =~ ^[0-9a-f]{40}$ ]] || { echo "::error::Unexpected head SHA: $PR_HEAD_SHA"; exit 1; }
+ echo "PR Tags: $PR_LABELS"
echo "Opt out of release notes: $OPT_OUT_RELEASE_NOTES"
+ _current_head_sha=$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" --jq '.head.sha')
+
+ if [[ "$_current_head_sha" != "$PR_HEAD_SHA" ]]; then
+ echo "::notice::Skipping stale release-note run for ${PR_HEAD_SHA}; current head is ${_current_head_sha}."
+ exit 0
+ fi
+
# VNEXT is a GitHub repository variable set via admin settings
# It controls the expected release notes version for FSharp.Core and FCS
- VNEXT="${{ vars.VNEXT }}"
if [[ -z "$VNEXT" ]]; then
echo "Error: VNEXT repository variable is not set. Please configure it in GitHub repository settings."
exit 1
@@ -60,10 +59,17 @@ jobs:
# Parse VS major version from eng/Versions.props for the vNext pattern
# 18
- _vs_major_version=$(grep -oPm1 "(?<=)[^<]+" eng/Versions.props)
+ _versions_props=$(
+ gh api \
+ -H 'Accept: application/vnd.github.raw+json' \
+ "repos/${GITHUB_REPOSITORY}/contents/eng/Versions.props?ref=${PR_BASE_SHA}"
+ )
+ _vs_major_version=$(
+ sed -n 's:.*\([^<]*\).*:\1:p' <<< "$_versions_props" \
+ | head -n 1
+ )
FSHARP_CORE_VERSION="$VNEXT"
- FCS_VERSION="$VNEXT"
VISUAL_STUDIO_VERSION="$_vs_major_version.vNext"
echo "Using VNEXT for release notes: ${VNEXT}"
@@ -81,7 +87,7 @@ jobs:
readonly paths=(
"src/FSharp.Core|${_fsharp_core_release_notes_path}"
"src/Compiler|${_fsharp_compiler_release_notes_path}"
- "LanguageFeatures.fsi|${_fsharp_language_release_notes_path}"
+ "src/Compiler/Facilities/LanguageFeatures.fsi|${_fsharp_language_release_notes_path}"
"vsintegration/src|${_fsharp_vs_release_notes_path}"
)
@@ -89,52 +95,101 @@ jobs:
RELEASE_NOTES_MESSAGE=""
RELEASE_NOTES_MESSAGE_DETAILS=""
RELEASE_NOTES_FOUND=""
- RELEASE_NOTES_CHANGES_SUMMARY=""
RELEASE_NOTES_NOT_FOUND=""
PULL_REQUEST_FOUND=true
- gh repo set-default ${GITHUB_REPOSITORY}
+ _modified_files=$(
+ gh api \
+ --method GET \
+ --paginate \
+ --slurp \
+ "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/files" \
+ -f per_page=100
+ )
+ _modified_count=$(jq '[.[][]] | length' <<< "$_modified_files")
+
+ # GitHub caps this endpoint at 3,000 files. At the cap the response may be
+ # incomplete, so fail closed instead of silently missing a protected path.
+ if (( _modified_count >= 3000 )); then
+ echo "::error::Cannot safely validate a PR with 3,000 or more changed files."
+ exit 1
+ fi
+
+ path_changed() {
+ jq -e --arg path "$1" \
+ 'any(.[][]; .filename == $path or (.filename | startswith($path + "/")))' \
+ <<< "$_modified_files" >/dev/null
+ }
- _modified_paths=`gh pr view ${PR_NUMBER} --json files --jq '.files.[].path'`
+ release_note_url() {
+ jq -r --arg file "$1" \
+ 'first(.[][] | select(.filename == $file and .status != "removed") | .contents_url) // empty' \
+ <<< "$_modified_files"
+ }
- for fields in ${paths[@]}
- do
+ record_missing_release_note() {
+ local path="$1"
+ local release_notes="$2"
+ local description="**No release notes found or release notes format is not correct**"
+ RELEASE_NOTES_NOT_FOUND+="| \\\`$path\\\` | [$release_notes](${FSHARP_REPO_URL}/tree/main/$release_notes) | ${description} |"
+ RELEASE_NOTES_NOT_FOUND+=$'\n'
+ }
+
+ for fields in "${paths[@]}"; do
IFS=$'|' read -r path release_notes <<< "$fields"
echo "Checking for changed files in: $path"
# Check if path is in modified files:
- if [[ "${_modified_paths[@]}" =~ "${path}" ]]; then
+ if path_changed "$path"; then
echo " Found $path in modified files"
echo " Checking if release notes modified in: $release_notes"
- if [[ "${_modified_paths[@]}" =~ "${release_notes}" ]]; then
+ if path_changed "$release_notes"; then
echo " Found $release_notes in modified files"
echo " Checking for pull request URL in $release_notes"
- if [[ ! -f $release_notes ]]; then
- echo " $release_notes does not exist, please, create it."
- #exit 1;
- fi
+ _release_note_url=$(release_note_url "$release_notes")
- _pr_link_occurences=`grep -c "${PR_URL}" $release_notes || true`
+ if [[ -n "$_release_note_url" ]]; then
+ if [[ "$_release_note_url" != "https://api.github.com/repos/${GITHUB_REPOSITORY}/contents/"* ]] \
+ || [[ "$_release_note_url" != *"?ref=${PR_HEAD_SHA}" ]]; then
+ echo "::error::Release-note content URL does not target the expected repository and PR head."
+ exit 1
+ fi
- echo " Found $_pr_link_occurences occurences of $PR_URL in $release_notes"
+ _release_note_file=$(mktemp)
- if [[ ${_pr_link_occurences} -eq 1 ]]; then
- echo " Found pull request URL in $release_notes once"
- RELEASE_NOTES_FOUND+="> | \\\`$path\\\` | [$release_notes](${FSHARP_REPO_URL}/tree/main/$release_notes) | |"
- RELEASE_NOTES_FOUND+=$'\n'
- elif [[ ${_pr_link_occurences} -eq 0 ]]; then
- echo " Did not find pull request URL in $release_notes"
- DESCRIPTION="**No current pull request URL (${PR_URL}) found, please consider adding it**"
- RELEASE_NOTES_FOUND+="> | \\\`$path\\\` | [$release_notes](${FSHARP_REPO_URL}/tree/main/$release_notes) | ${DESCRIPTION} |"
- RELEASE_NOTES_FOUND+=$'\n'
- PULL_REQUEST_FOUND=false
+ if ! gh api \
+ -H 'Accept: application/vnd.github.raw+json' \
+ "$_release_note_url" > "$_release_note_file"
+ then
+ rm -f "$_release_note_file"
+ echo "::error::Unable to read $release_notes at PR head $PR_HEAD_SHA."
+ exit 1
+ fi
+
+ _pr_link_occurrences=$(grep -Fc -- "$PR_URL" "$_release_note_file" || true)
+ rm -f "$_release_note_file"
+
+ echo " Found $_pr_link_occurrences occurrences of $PR_URL in $release_notes"
+
+ if [[ ${_pr_link_occurrences} -eq 1 ]]; then
+ echo " Found pull request URL in $release_notes once"
+ RELEASE_NOTES_FOUND+="> | \\\`$path\\\` | [$release_notes](${FSHARP_REPO_URL}/tree/main/$release_notes) | |"
+ RELEASE_NOTES_FOUND+=$'\n'
+ elif [[ ${_pr_link_occurrences} -eq 0 ]]; then
+ echo " Did not find pull request URL in $release_notes"
+ DESCRIPTION="**No current pull request URL (${PR_URL}) found, please consider adding it**"
+ RELEASE_NOTES_FOUND+="> | \\\`$path\\\` | [$release_notes](${FSHARP_REPO_URL}/tree/main/$release_notes) | ${DESCRIPTION} |"
+ RELEASE_NOTES_FOUND+=$'\n'
+ PULL_REQUEST_FOUND=false
+ fi
+ else
+ echo " $release_notes was removed or cannot be read at the PR head."
+ record_missing_release_note "$path" "$release_notes"
fi
else
echo " Did not find $release_notes in modified files"
- DESCRIPTION="**No release notes found or release notes format is not correct**"
- RELEASE_NOTES_NOT_FOUND+="| \\\`$path\\\` | [$release_notes](${FSHARP_REPO_URL}/tree/main/$release_notes) | ${DESCRIPTION} |"
- RELEASE_NOTES_NOT_FOUND+=$'\n'
+ record_missing_release_note "$path" "$release_notes"
fi
else
echo " Nothing found, no release notes required"
@@ -220,60 +275,81 @@ jobs:
RELEASE_NOTES_MESSAGE+=$RELEASE_NOTES_MESSAGE_DETAILS
fi
- echo "release-notes-check-message<<$EOF" >>$GITHUB_OUTPUT
-
- if [[ "$OPT_OUT_RELEASE_NOTES" = true ]]; then
- echo "" >>$GITHUB_OUTPUT
- echo "" >>$GITHUB_OUTPUT
- echo "## :warning: Release notes required, but author opted out" >>$GITHUB_OUTPUT
- echo "" >>$GITHUB_OUTPUT
- echo "" >>$GITHUB_OUTPUT
- echo "> [!WARNING]" >>$GITHUB_OUTPUT
- echo "> **Author opted out of release notes, check is disabled for this pull request.**" >>$GITHUB_OUTPUT
- echo "> cc @dotnet/fsharp-team-msft" >>$GITHUB_OUTPUT
- else
- echo "${RELEASE_NOTES_MESSAGE}" >>$GITHUB_OUTPUT
+ _current_head_sha=$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" --jq '.head.sha')
+
+ if [[ "$_current_head_sha" != "$PR_HEAD_SHA" ]]; then
+ echo "::notice::Discarding stale release-note result for ${PR_HEAD_SHA}; current head is ${_current_head_sha}."
+ exit 0
fi
- echo "$EOF" >>$GITHUB_OUTPUT
+ {
+ echo "release-notes-check-message<<$EOF"
+
+ if [[ "$OPT_OUT_RELEASE_NOTES" = true ]]; then
+ echo ""
+ echo ""
+ echo "## :warning: Release notes required, but author opted out"
+ echo ""
+ echo ""
+ echo "> [!WARNING]"
+ echo "> **Author opted out of release notes, check is disabled for this pull request.**"
+ echo "> cc @dotnet/fsharp-team-msft"
+ else
+ echo "${RELEASE_NOTES_MESSAGE}"
+ fi
+
+ echo "$EOF"
+ } >> "$GITHUB_OUTPUT"
if [[ $RELEASE_NOTES_NOT_FOUND != "" && ${OPT_OUT_RELEASE_NOTES} != true ]]; then
exit 1
fi
- # Did bot already commented the PR?
- - name: Find Comment
- if: success() || failure()
- uses: peter-evans/find-comment@v2.4.0
- id: fc
- with:
- issue-number: ${{github.event.pull_request.number}}
- comment-author: 'github-actions[bot]'
- body-includes: ''
- # If not, create a new comment
- - name: Create comment
- if: steps.fc.outputs.comment-id == '' && (success() || failure())
- uses: actions/github-script@v6
+ # Keep one bot comment current without evaluating pull request content as JavaScript.
+ # Posting the informational comment is best-effort and must never fail the check:
+ # this job runs via pull_request_target, and the Actions GITHUB_TOKEN is not always
+ # permitted to create a new issue comment (the comment API can return HTTP 403
+ # "Resource not accessible by integration"), even though release-notes validation
+ # above has already succeeded.
+ - name: Create or update comment
+ if: ${{ (success() || failure()) && steps.release_notes_changes.outputs.release-notes-check-message != '' }}
+ continue-on-error: true
+ uses: actions/github-script@v9
+ env:
+ COMMENT_BODY: ${{ steps.release_notes_changes.outputs.release-notes-check-message }}
with:
github-token: ${{ github.token }}
script: |
- const comment = await github.rest.issues.createComment({
- issue_number: context.issue.number,
- owner: context.repo.owner,
- repo: context.repo.repo,
- body: `${{steps.release_notes_changes.outputs.release-notes-check-message}}`
- });
- return comment.data.id;
- # If yes, update the comment
- - name: Update comment
- if: steps.fc.outputs.comment-id != '' && (success() || failure())
- uses: actions/github-script@v6
- with:
- github-token: ${{ github.token }}
- script: |
- const comment = await github.rest.issues.updateComment({
- owner: context.repo.owner,
- repo: context.repo.repo,
- comment_id: ${{steps.fc.outputs.comment-id}},
- body: `${{steps.release_notes_changes.outputs.release-notes-check-message}}`
- });
- return comment.data.id;
\ No newline at end of file
+ const marker = '';
+ try {
+ const comments = await github.paginate(github.rest.issues.listComments, {
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: context.issue.number,
+ per_page: 100
+ });
+ const existing = comments.find(comment =>
+ comment.user?.login === 'github-actions[bot]' && comment.body?.includes(marker));
+
+ if (existing) {
+ const comment = await github.rest.issues.updateComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ comment_id: existing.id,
+ body: process.env.COMMENT_BODY
+ });
+ return comment.data.id;
+ }
+
+ const comment = await github.rest.issues.createComment({
+ issue_number: context.issue.number,
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ body: process.env.COMMENT_BODY
+ });
+ return comment.data.id;
+ } catch (error) {
+ // The comment is informational only. The release-notes verdict is enforced by the
+ // "Check for release notes changes" step, so never fail the job if posting fails
+ // (e.g. a read-only token on some pull requests).
+ core.warning(`Unable to post release-notes comment: ${error.message}`);
+ }
diff --git a/Directory.Build.targets b/Directory.Build.targets
index 4e5dab341de..a0ac2867bd2 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -3,6 +3,13 @@
+
+
+ $(NoWarn);NU1507
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/Directory.Packages.props b/Directory.Packages.props
new file mode 100644
index 00000000000..80c569422a8
--- /dev/null
+++ b/Directory.Packages.props
@@ -0,0 +1,10 @@
+
+
+
+ true
+ true
+
+
+
+
+
diff --git a/NuGet.config b/NuGet.config
index 527f95b5c87..a6df74bb15e 100644
--- a/NuGet.config
+++ b/NuGet.config
@@ -35,4 +35,8 @@
+
+
+
+
diff --git a/azure-pipelines-PR.yml b/azure-pipelines-PR.yml
index 827b97f33ac..65f45277382 100644
--- a/azure-pipelines-PR.yml
+++ b/azure-pipelines-PR.yml
@@ -65,8 +65,6 @@ variables:
value: Products/$(System.TeamProject)/$(Build.Repository.Name)/$(Build.SourceBranchName)/$(Build.BuildNumber)
- name: Codeql.Enabled
value: true
- - ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}:
- - group: DotNet-FSharp-SDLValidation-Params
- ${{ if and(eq(variables['System.TeamProject'], 'public'), eq(variables['Build.Reason'], 'PullRequest')) }}:
- name: RunningAsPullRequest
value: true
@@ -341,78 +339,6 @@ stages:
ArtifactType: Container
parallel: true
- - job: WindowsStrictIndentation
- pool:
- name: $(DncEngPublicBuildPool)
- demands: ImageOverride -equals $(_WindowsMachineQueueName)
- timeoutInMinutes: 120
- steps:
- - checkout: self
- clean: true
-
- - script: eng\CIBuildNoPublish.cmd -compressallmetadata -configuration Release /p:AdditionalFscCmdFlags=--strict-indentation+
- env:
- DOTNET_DbgEnableMiniDump: 1
- DOTNET_DbgMiniDumpType: 2 # 1=mini, 2=heap, 3=triage, 4=full. Heap dumps include managed object data for debugging.
- DOTNET_DbgMiniDumpName: $(Build.SourcesDirectory)\artifacts\log\Release\$(Build.BuildId)-%e-%p-%t.dmp
- NativeToolsOnMachine: true
- displayName: Build
-
- - task: PublishBuildArtifacts@1
- displayName: Publish Build BinLog
- condition: always()
- continueOnError: true
- inputs:
- PathToPublish: '$(Build.SourcesDirectory)\artifacts\log/Release\Build.VisualFSharp.slnx.binlog'
- ArtifactName: 'Windows Release build binlogs'
- ArtifactType: Container
- parallel: true
- - task: PublishBuildArtifacts@1
- displayName: Publish Dumps
- condition: failed()
- continueOnError: true
- inputs:
- PathToPublish: '$(Build.SourcesDirectory)\artifacts\log\Release'
- ArtifactName: 'Windows Release WindowsStrictIndentation process dumps'
- ArtifactType: Container
- parallel: true
-
- - job: WindowsNoStrictIndentation
- pool:
- name: $(DncEngPublicBuildPool)
- demands: ImageOverride -equals $(_WindowsMachineQueueName)
- timeoutInMinutes: 120
- steps:
- - checkout: self
- clean: true
-
- - script: eng\CIBuildNoPublish.cmd -compressallmetadata -configuration Release /p:AdditionalFscCmdFlags=--strict-indentation-
- env:
- DOTNET_DbgEnableMiniDump: 1
- DOTNET_DbgMiniDumpType: 2 # 1=mini, 2=heap, 3=triage, 4=full. Heap dumps include managed object data for debugging.
- DOTNET_DbgMiniDumpName: $(Build.SourcesDirectory)\artifacts\log\Release\$(Build.BuildId)-%e-%p-%t.dmp
- NativeToolsOnMachine: true
- displayName: Build
-
- - task: PublishBuildArtifacts@1
- displayName: Publish Build BinLog
- condition: always()
- continueOnError: true
- inputs:
- PathToPublish: '$(Build.SourcesDirectory)\artifacts\log/Release\Build.VisualFSharp.slnx.binlog'
- ArtifactName: 'Windows Release build binlogs'
- ArtifactType: Container
- parallel: true
- - task: PublishBuildArtifacts@1
- displayName: Publish Dumps
- condition: failed()
- continueOnError: true
- inputs:
- PathToPublish: '$(Build.SourcesDirectory)\artifacts\log\Release'
- ArtifactName: 'Windows Release WindowsNoStrictIndentation process dumps'
- ArtifactType: Container
- parallel: true
-
# Windows With Compressed Metadata
- job: WindowsCompressedMetadata
variables:
@@ -774,7 +700,7 @@ stages:
workingDirectory: $(Build.SourcesDirectory)
installationPath: $(Build.SourcesDirectory)/.dotnet
- script: .\eng\common\dotnet.cmd
- - script: .\Build.cmd $(_kind) -pack -c $(_BuildConfig)
+ - script: .\Build.cmd $(_kind) -ci -pack -c $(_BuildConfig)
env:
NativeToolsOnMachine: true
displayName: Initial build and prepare packages.
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 6fdbd2ca6cb..932af868c73 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -48,7 +48,6 @@ variables:
value: Products/$(System.TeamProject)/$(Build.Repository.Name)/$(Build.SourceBranchName)/$(Build.BuildNumber)
- name: Codeql.Enabled
value: "true"
- - group: DotNet-FSharp-SDLValidation-Params
- template: /eng/common/templates-official/variables/pool-providers.yml@self
resources:
@@ -68,6 +67,7 @@ extends:
enabled: true
policheck:
enabled: true
+ exclusionsFile: '$(Build.SourcesDirectory)/eng/policheck_exclusions.xml'
sbom:
enabled: false # VS SBOM is generated with other steps
justificationForDisabling: 'SBOM for F# is generated via build process. Will be migrated at later date.'
@@ -219,23 +219,8 @@ extends:
enableSymbolValidation: false
# SourceLink improperly looks for generated files. See https://github.com/dotnet/arcade/issues/3069
enableSourceLinkValidation: false
- # Enable SDL validation, passing through values from the 'DotNet-FSharp-SDLValidation-Params' group.
- SDLValidationParameters:
- enable: true
- params: >-
- -SourceToolsList @("policheck","credscan")
- -ArtifactToolsList @("binskim")
- -BinskimAdditionalRunConfigParams @("IgnorePdbLoadError < True","Recurse < True")
- -TsaInstanceURL $(_TsaInstanceURL)
- -TsaProjectName $(_TsaProjectName)
- -TsaNotificationEmail $(_TsaNotificationEmail)
- -TsaCodebaseAdmin $(_TsaCodebaseAdmin)
- -TsaBugAreaPath $(_TsaBugAreaPath)
- -TsaIterationPath $(_TsaIterationPath)
- -TsaRepositoryName "FSharp"
- -TsaCodebaseName "FSharp-GitHub"
- -TsaPublish $True
- -PoliCheckAdditionalRunConfigParams @("UserExclusionPath < $(Build.SourcesDirectory)/eng/policheck_exclusions.xml")
+ # SDL validation (PoliCheck, CredScan, BinSkim) and TSA reporting are handled by the 1ES Pipeline
+ # Templates via the 'sdl:' block in the 'extends' section above; TSA config lives in eng/TSAConfig.gdntsa.
#---------------------------------------------------------------------------------------------------------------------#
# VS Insertion #
diff --git a/buildtools/AssemblyCheck/AssemblyCheck.fsproj b/buildtools/AssemblyCheck/AssemblyCheck.fsproj
index 78d24349889..8023580df5a 100644
--- a/buildtools/AssemblyCheck/AssemblyCheck.fsproj
+++ b/buildtools/AssemblyCheck/AssemblyCheck.fsproj
@@ -23,7 +23,7 @@
-
+
diff --git a/buildtools/checkpackages/Directory.Build.props b/buildtools/checkpackages/Directory.Build.props
index a9a651c4a65..1aa11050403 100644
--- a/buildtools/checkpackages/Directory.Build.props
+++ b/buildtools/checkpackages/Directory.Build.props
@@ -3,6 +3,8 @@
+
+ falsetrue$(MSBuildProjectDirectory)\..\..\artifacts\tmp\$([System.Guid]::NewGuid())$(CachePath)\obj\
diff --git a/buildtools/fslex/fslex.fsproj b/buildtools/fslex/fslex.fsproj
index 3b8aafb532b..08f77151636 100644
--- a/buildtools/fslex/fslex.fsproj
+++ b/buildtools/fslex/fslex.fsproj
@@ -38,7 +38,7 @@
-
+
diff --git a/buildtools/fsyacc/fsyacc.fsproj b/buildtools/fsyacc/fsyacc.fsproj
index ba57de811c9..42ea6e1bf36 100644
--- a/buildtools/fsyacc/fsyacc.fsproj
+++ b/buildtools/fsyacc/fsyacc.fsproj
@@ -38,7 +38,7 @@
-
+
diff --git a/docs/fcs-samples/Directory.Build.props b/docs/fcs-samples/Directory.Build.props
new file mode 100644
index 00000000000..21aa3b5274e
--- /dev/null
+++ b/docs/fcs-samples/Directory.Build.props
@@ -0,0 +1,7 @@
+
+
+
+
+ false
+
+
diff --git a/docs/reflectionfree-printing.md b/docs/reflectionfree-printing.md
new file mode 100644
index 00000000000..68e3091faf6
--- /dev/null
+++ b/docs/reflectionfree-printing.md
@@ -0,0 +1,73 @@
+# Simple vs Reflection-based DU and Record printing
+
+This document describes two modes for printing Discriminated Unions (DUs) and Records in F#: a **simple** reflection-free mode that delegates to a `string`-like operator for printing field values, and a `sprintf` mode (`sprintf "%A"`), which uses **reflection** to create output looking like F# code. In this document, the terms *simple* and *reflection* are used to distinguish the two modes.
+
+Without the `--reflectionfree` flag, the compiler generates a `ToString` for DUs and Records that calls `sprintf "%A"`. With the flag, the compiler generates a `ToString` that uses the simple mode.
+
+Users can choose between the two modes by 1. use of `--reflectionfree`, and by 2. calling with a `sprintf`-type caller or a `string`-type caller (e.g. the `string` operator, `ToString`, or interpolated strings).
+
+If `x` is a DU or Record, then output will be simple or reflection-based as follows:
+| | `--reflectionfree` | no `--reflectionfree` |
+|---|---|---|
+| `string x` | simple | reflection |
+| `x.ToString()` | simple | reflection |
+| `$"{x}"` | simple | reflection |
+| `sprintf "%A" x` | disallowed (would be reflection) | reflection |
+
+As such, the current default reflection `ToString` generation forces reflection formatting on all callers. On the other hand, generating simple `ToString` output means that the records and DUs are printed with simple or reflection formatting depending on whether the caller is of simple or reflection affinity. The `--reflectionfree` flag combines this property with a ban on `sprintf` to prevent the reflection mode from being used.
+
+In addition to user-defined types, the FSharp.Core `option` type uses simple printing, while other types either have no `ToString` or use some other format.
+
+## Behaviour: definitions
+
+In simple printing, field values are printed with `string`-type formatting, more precisely `anyToStringShowingNull`. No line breaks are inserted.
+
+- **Record**: `{ Name1 = value1; Name2 = value2 }`.
+- **Anonymous record**: the same, but with `{| ` and ` |}`.
+- **Union**: A case with no fields renders as just its name. A case with fields renders as `CaseName(value1, value2)`.
+
+`[]` records and unions, and struct anonymous records, render identically to their reference-type forms.
+
+A type that supplies its own `ToString` override keeps it, with no `ToString` generated for it (either simple or reflection).
+
+Reflection-mode printing is described in [plain text formatting](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/plaintext-formatting).
+
+## Behavioural differences
+
+### Differences in field rendering
+
+The following differences between `string` and `sprintf "%A"` carry over directly into differences in field rendering between simple and reflection printing:
+
+| F# value | simple (`anyToStringShowingNull`) | reflection (`sprintf "%A"`) |
+|---|---|---|
+| string field `"hi"` | `hi` | `"hi"` |
+| char field `'a'` | `a` | `'a'` |
+| float `5.0` | `5` | `5.0` |
+| `250uy` / `42n` / `1.5M` | `250` / `42` / `1.5` | `250uy` / `42n` / `1.5M` |
+| option field `None` | `null` | `None` |
+| array field `[\|1;2;3\|]` | `System.Int32[]` | `[\|1; 2; 3\|]` |
+| unit field `()` | `null` | `()` |
+
+The overall differences here are:
+- Simple printing converts to strings, while reflection printing is more bi-directional, often generating compilable F# code.
+- F# types that have null representation (`unit`, `option`, and in general types with `AllowNullLiteral` or `UseNullAsTrueValue`) are printed as `null` in simple printing, while reflection printing uses a more F#-like representation.
+
+### Other differences
+
+These differences are in the printing of the record or DU itself rather than of its fields:
+
+| F# value | simple (`string`) | reflection (`sprintf "%A"`) |
+|---|---|---|
+| `B 5` (single field) | `B(5)` | `B 5` |
+| `C (3, 4)` (two fields) | `C(3, 4)` | `C (3, 4)` |
+| record `{ X = 1; Y = 2 }` | `{ X = 1; Y = 2 }` | `{ X = 1`⏎` Y = 2 }` |
+| `[")>]` | `{ X = 5 }` | `Custom<5>` |
+
+The overall differences here are:
+- Simple printing always brackets a case's fields and never pads, while reflection printing omits brackets for a single non-tuple field and inserts a space before them otherwise.
+- Simple printing uses a single line (unless a field's own rendering contains breaks), while reflection printing breaks records and nested values across lines with indentation.
+- `StructuredFormatDisplay` is ignored in simple printing and honoured in reflection printing.
+
+## Recursion and depth
+
+Rendering recurses into nested records and unions. Deep nesting is guarded by `RuntimeHelpers.EnsureSufficientExecutionStack`, raising a catchable `InsufficientExecutionStackException` rather than `StackOverflowException`; cycles (which require mutation to construct) still overflow, as `option` and `list` do.
\ No newline at end of file
diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
index 47210a580fd..52698cc182b 100644
--- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
+++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
@@ -1,5 +1,8 @@
### Fixed
+* Fix incorrect `StructLayout(Size = 1)` emission for data-less struct unions where the compiler-generated tag field makes the actual runtime size larger. ([PR #19759](https://github.com/dotnet/fsharp/pull/19759))
+* Fix FS0750 "This construct may only be used within computation expressions" incorrectly raised for `let!`/`use!`/`do!` appearing in the right-hand side of a plain `let` binding inside a computation expression. The right-hand side is now desugared as a nested computation of the same builder whose result is bound with `let!`, keeping its bindings correctly scoped. ([Issue #19457](https://github.com/dotnet/fsharp/issues/19457), [PR #19868](https://github.com/dotnet/fsharp/pull/19868))
+* Stop leaking a `System.Diagnostics.Metrics.MeterListener` per `Cache` in DEBUG builds. Each cache created a `CacheMetrics.CacheMetricsListener` (which starts a `MeterListener` registered in the process-global metrics registry) and never disposed it, so listeners accumulated for the lifetime of the process. Because every cache hit/miss/add published to all registered listeners, the per-operation cost grew linearly with the number of leaked listeners, so repeated checks (and Debug FCS test runs) slowed down over time. The per-cache `CacheMetricsListener` and the per-instance `cacheId` tag are removed; `DebugDisplay` and tests now read the existing name-aggregated stats populated by the single `ListenToAll` listener, so no per-cache listener is created and no per-operation cost is added. ([PR #19995](https://github.com/dotnet/fsharp/pull/19995))
* Fix state machine lowering dropping the side-effectful receiver of an unused unit-typed member access (e.g. inside `task { (effectful()).UnitProp }`). ([Issue #13099](https://github.com/dotnet/fsharp/issues/13099), [PR #19885](https://github.com/dotnet/fsharp/pull/19885))
* `--deterministic` Release builds now produce byte-identical `FSharp.Compiler.Service.dll` under `--parallelcompilation+` and `--parallelcompilation-`, so it is restored to the determinism gate (now also checked sequential-vs-parallel). Code generation runs the same deferred per-file drain in both modes, with type/member/field emit-order keys and generated names derived from the file being emitted rather than thread-scheduling order. ([Issue #19928](https://github.com/dotnet/fsharp/issues/19928), [PR #19929](https://github.com/dotnet/fsharp/pull/19929))
* Fix `[]` silently producing duplicate IL entries (FS0192/FS2014) when applied to a multi-value let-binding (e.g. `let a, b = 1, 2`); now emits FS0755 at type-check time. ([Issue #6131](https://github.com/dotnet/fsharp/issues/6131), [PR #19924](https://github.com/dotnet/fsharp/pull/19924))
@@ -116,6 +119,7 @@
* Warn FS3888 when a compiler-semantic attribute on a value/member or type/module is present in the `.fs` but missing from the `.fsi`. Such attributes were previously ignored at the consumer side. Under the `ErrorOnMissingSignatureAttribute` preview language feature, FS3888 is an error. ([Issue #19560](https://github.com/dotnet/fsharp/issues/19560), [PR #19880](https://github.com/dotnet/fsharp/pull/19880))
* Emit debug points at a stack-empty position ([PR #19877](https://github.com/dotnet/fsharp/pull/19877))
* Fix spurious XmlDoc warnings (unknown parameter / no documentation for parameter) under `--warnon:3390` when a get/set property documents the full parameter set across both accessors. ([Issue #13684](https://github.com/dotnet/fsharp/issues/13684), [PR #19884](https://github.com/dotnet/fsharp/pull/19884))
+* Replace internal compiler error FS0193 with a clear FS3891 diagnostic when a type abbreviation aliases a generic attribute type (e.g. `type B = A` then `[] ...`). Generic attributes remain unsupported in F#. ([Issue #7877](https://github.com/dotnet/fsharp/issues/7877), [PR #19915](https://github.com/dotnet/fsharp/pull/19915))
* Fix Go to Metadata rendering of IL literal (`const`) fields - they now appear with `[]` and their constant value, e.g. `System.Char.MaxValue` no longer shows as a plain `static val`. ([Issue #11526](https://github.com/dotnet/fsharp/issues/11526), [PR #19922](https://github.com/dotnet/fsharp/pull/19922))
* FSI multi-assembly emit (`--multiemit+`) now attaches `System.Diagnostics.DebuggableAttribute(DisableOptimizations|Default)` to each submission's manifest when local optimizations are disabled (`--optimize-`), matching the single-emit and regular-compiler behavior so debuggers see submissions as unoptimized. ([Issue #14572](https://github.com/dotnet/fsharp/issues/14572), [PR #19921](https://github.com/dotnet/fsharp/pull/19921))
* Stop F# Interactive from mutating script arguments that follow `--`. Abbreviated flags like `-d`, `-r`, `-I` after the `--` separator are no longer colon-joined with their next token in `fsi.CommandLineArgs`. ([Issue #10819](https://github.com/dotnet/fsharp/issues/10819), [PR #19926](https://github.com/dotnet/fsharp/pull/19926))
@@ -124,9 +128,11 @@
* Fix FSI pretty printing to distinguish anonymous records (`{| ... |}`) from nominal records (`{ ... }`). ([Issue #6116](https://github.com/dotnet/fsharp/issues/6116), [PR #19919](https://github.com/dotnet/fsharp/pull/19919))
* Fix dot-completion after indexed expressions (`a.[0].Data.`, `a[0].Data.`, `[1;2].Length.`) returning unrelated global completions instead of expression-typings members. ([Issue #4966](https://github.com/dotnet/fsharp/issues/4966), [PR #19934](https://github.com/dotnet/fsharp/pull/19934))
* Quotations of `match s with "" -> _` no longer leak the `s <> null && s.Length = 0` lowering; the empty-string optimization moved from pattern-match compilation to the optimizer so quoted expressions keep `op_Equality(s, "")`. ([Issue #19873](https://github.com/dotnet/fsharp/issues/19873))
+* Fix #5795: Allow attributes defined in a `module rec` / `namespace rec` scope to be used on union cases, record fields, and generic type parameters of types in the same recursive scope. ([Issue #5795](https://github.com/dotnet/fsharp/issues/5795), [PR #19744](https://github.com/dotnet/fsharp/pull/19744))
### Added
+* Added internal synthesized-name replay infrastructure for compiler-generated names, preserving normal compilation output while enabling future hot reload name stability work.
* Added `FSharpMemberOrFunctionOrValue.IsPropertyAccessor` convenience property that returns true for compiler-generated property accessors (`get_X` / `set_X`). ([Issue #18157](https://github.com/dotnet/fsharp/issues/18157), [PR #19883](https://github.com/dotnet/fsharp/pull/19883))
* Added warning FS3884 when a function or delegate value is used as an interpolated string argument. ([PR #19289](https://github.com/dotnet/fsharp/pull/19289))
* Symbols: add ObsoleteDiagnosticInfo ([PR #19359](https://github.com/dotnet/fsharp/pull/19359))
@@ -135,18 +141,29 @@
* Add diagnostic FS3889 when a namespace and a type have the same fully-qualified name in the same assembly, replacing the misleading FS0247 "namespace and a module" error. ([Issue #17827](https://github.com/dotnet/fsharp/issues/17827), [PR #19802](https://github.com/dotnet/fsharp/pull/19802))
* Debug: rework for expressions stepping ([PR #19894](https://github.com/dotnet/fsharp/pull/19894))
* Debug: rework conditional erasure, fix stepping over literals ([PR #19897](https://github.com/dotnet/fsharp/pull/19897))
+* Spread operator for records ([RFC FS-1151](https://github.com/fsharp/fslang-design/pull/805), [PR #18927](https://github.com/dotnet/fsharp/pull/18927))
* Debug: fix if and match condition sequence points ([PR #19932](https://github.com/dotnet/fsharp/pull/19932))
+* Under `--reflectionfree`, discriminated unions, records and anonymous records now get a [generated `ToString`](../../reflectionfree-printing.md) (rendering each field like `Option` does) instead of falling back to the namespace-qualified type name. ([PR #19976](https://github.com/dotnet/fsharp/pull/19976))
+* Support common types of `NotNullIfNotNullAttribute` usage. If a method parameter is marked with `NotNullIfNotNullAttribute`, the compiler will now honor this attribute and mark the return type as non-null. ([PR #19977](https://github.com/dotnet/fsharp/pull/19977))
* Checker: recover on checking language version ([PR ##19970](https://github.com/dotnet/fsharp/pull/19970))
* Implied argument names for function-to-delegate coercions now fall back to the delegate's `Invoke` parameter names when the function has no recoverable names (e.g. a partial application like `System.Func((+) 1)`), instead of synthetic `delegateArg0`, `delegateArg1`, … names. ([PR #20001](https://github.com/dotnet/fsharp/pull/20001))
+* Add internal `ResetCompilerGeneratedNameState` to `CompilerGlobalState` name generators so warm-checker re-compilation can produce fresh-process-identical generated names. ([PR #20017](https://github.com/dotnet/fsharp/pull/20017))
+* Add Roslyn-format EnC CustomDebugInformation codec and portable PDB method CDI emission support to AbstractIL. ([PR #20018](https://github.com/dotnet/fsharp/pull/20018))
### Improved
* Nullness warning FS3261 on dotted method or property access (e.g. `x.Member`) now underlines the receiver expression and includes the member name and (when known) the binding name in the message. ([Issue #19658](https://github.com/dotnet/fsharp/issues/19658), [PR #19814](https://github.com/dotnet/fsharp/pull/19814))
+* Direct delegate construction ([PR ##19993](https://github.com/dotnet/fsharp/pull/19993))
### Changed
* Improvements in error and warning messages: new error FS3885 when `let!`/`use!` is the final expression in a computation expression; new warning FS3886 when a list literal contains a single tuple element (likely missing `;` separator); improved wording for FS0003, FS0025, FS0039, FS0072, FS0247, FS0597, FS0670, FS3082, and SRTP operator-not-in-scope hints. ([PR #19398](https://github.com/dotnet/fsharp/pull/19398))
* Exception field serialization (`GetObjectData` and field-restoring constructor) is now gated behind `langversion:11` (`LanguageFeature.ExceptionFieldSerializationSupport`). With langversion ≤10, exception codegen is unchanged from pre-#19342 behavior. ([PR #19746](https://github.com/dotnet/fsharp/pull/19746))
+* Lower string-typed interpolated strings to `System.String.Concat` rather than the reflection-based `printf` engine, making them trim- and NativeAOT-compatible. This generalizes and ungates the previous all-string `String.Concat` optimization, so it now applies to every string-typed interpolation. ([Language suggestion #1108](https://github.com/fsharp/fslang-suggestions/issues/1108), [PR #19971](https://github.com/dotnet/fsharp/pull/19971))
+* Interpolated string holes (e.g. `$"{x}"`) are now formatted with invariant culture (via the `string` operator) instead of the current thread culture. ([PR #19971](https://github.com/dotnet/fsharp/pull/19971))
### Breaking Changes
-* Optimizer: don't inline named functions in debug builds ([PR #19548](https://github.com/dotnet/fsharp/pull/19548)
\ No newline at end of file
+
+* `FSharp.Compiler.Syntax.SynInterpolatedStringPart.FillExpr` now carries a `SynInterpolationFormatting` value (separating .NET alignment/format from printf specifiers) instead of an `Ident option`. ([PR #19971](https://github.com/dotnet/fsharp/pull/19971))
+* Optimizer: don't inline named functions in debug builds ([PR #19548](https://github.com/dotnet/fsharp/pull/19548)
+* LexFilter: drop non-strict mode ([PR #20106](https://github.com/dotnet/fsharp/pull/20106))
diff --git a/docs/release-notes/.Language/preview.md b/docs/release-notes/.Language/preview.md
index 8172d510f76..30df5427619 100644
--- a/docs/release-notes/.Language/preview.md
+++ b/docs/release-notes/.Language/preview.md
@@ -3,9 +3,16 @@
* Warn (FS3884) when a function or delegate value is used as an interpolated string argument, since it will be formatted via `ToString` rather than being applied. ([PR #19289](https://github.com/dotnet/fsharp/pull/19289))
* Added `MethodOverloadsCache` language feature (preview) that caches overload resolution results for repeated method calls, significantly improving compilation performance. ([PR #19072](https://github.com/dotnet/fsharp/pull/19072))
* Added `ErrorOnMissingSignatureAttribute` preview language feature: makes FS3888 (compiler-semantic attribute on the `.fs` but not on the `.fsi`) an error instead of a warning. ([Issue #19560](https://github.com/dotnet/fsharp/issues/19560), [PR #19880](https://github.com/dotnet/fsharp/pull/19880))
+* Support common types of `NotNullIfNotNullAttribute` usage. If a method parameter is marked with `NotNullIfNotNullAttribute`, the compiler will now honor this attribute and mark the return type as non-null. ([PR #19977](https://github.com/dotnet/fsharp/pull/19977))
+* Spread operator for records ([RFC FS-1151](https://github.com/fsharp/fslang-design/pull/805), [PR #18927](https://github.com/dotnet/fsharp/pull/18927))
* Added `AccessProtectedBaseFieldFromClosure` preview language feature: a derived member can now read a `protected` base-class field from an ordinary closure (lambda, delegate, `async`/`seq`/`lazy`, `function`, or list/array literal), which previously failed with FS1097 even though direct access compiles. Object expressions remain unsupported — bind the field to a local function or expose it through a member. ([Issue #5302](https://github.com/dotnet/fsharp/issues/5302))
* Added `ImprovedImpliedArgumentNamesPartTwo` language feature: when a function with no recoverable parameter names is coerced to a delegate (e.g. a partial application like `System.Func((+) 1)`), the synthesized `Invoke` parameters take their names from the delegate's own `Invoke` signature instead of synthetic `delegateArg0`, `delegateArg1`, … names. ([PR #20001](https://github.com/dotnet/fsharp/pull/20001))
### Fixed
-### Changed
\ No newline at end of file
+### Changed
+
+* Direct delegate construction ([PR #19993](https://github.com/dotnet/fsharp/pull/19993))
+ * A delegate built from a method or function now points straight at that method instead of an intermediate closure, so `delegate.Method` is the real target and no closure class is generated.
+ * Two delegates built from the same method and target now compare equal, where the previous closure form produced distinct instances; this also makes `Delegate.Remove` (and `-=` on events) match and remove such a delegate that it previously left in place.
+ * A `null` instance receiver now faults at delegate construction rather than at the first invoke: an `ArgumentException` for a non-virtual target (the delegate constructor rejects a null `this`) or a `NullReferenceException` for a virtual one (from `ldvirtftn`), matching how C# builds the same delegate.
diff --git a/docs/release-notes/.VisualStudio/18.vNext.md b/docs/release-notes/.VisualStudio/18.vNext.md
index 6205e8caef0..0166a73a6d9 100644
--- a/docs/release-notes/.VisualStudio/18.vNext.md
+++ b/docs/release-notes/.VisualStudio/18.vNext.md
@@ -15,3 +15,4 @@
* Rename "inline hints" to "inlay hints" in VS options for consistency with industry terminology. ([PR #19318](https://github.com/dotnet/fsharp/pull/19318))
* Unused analyzers: disable in VS when file has errors ([PR #19892](https://github.com/dotnet/fsharp/pull/19892))
+* Move to Roslyn's unified ExternalAccess library ([PR #20099](https://github.com/dotnet/fsharp/pull/20099))
diff --git a/eng/Packages.props b/eng/Packages.props
new file mode 100644
index 00000000000..b609655af51
--- /dev/null
+++ b/eng/Packages.props
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/eng/TargetFrameworks.props b/eng/TargetFrameworks.props
index d384e5fbcaa..e3938d0f73a 100644
--- a/eng/TargetFrameworks.props
+++ b/eng/TargetFrameworks.props
@@ -11,7 +11,7 @@
- net10.0
+ net11.0$([System.Text.RegularExpressions.Regex]::Replace('$(FSharpNetCoreProductTargetFramework)', '^net(\d+)\.0$', '$1'))
diff --git a/eng/Version.Details.props b/eng/Version.Details.props
index 90530fac6e9..88eb83473d6 100644
--- a/eng/Version.Details.props
+++ b/eng/Version.Details.props
@@ -6,7 +6,7 @@ This file should be imported by eng/Versions.props
- 10.0.0-beta.26379.2
+ 10.0.0-beta.26403.218.10.0-preview-26357-0818.10.0-preview-26357-08
@@ -19,20 +19,20 @@ This file should be imported by eng/Versions.props
1.0.0-prerelease.26318.11.0.0-prerelease.26318.1
- 5.10.0-1.26357.6
- 5.10.0-1.26357.6
- 5.10.0-1.26357.6
- 5.10.0-1.26357.6
- 5.10.0-1.26357.6
- 5.10.0-1.26357.6
- 5.10.0-1.26357.6
- 5.10.0-1.26357.6
+ 5.10.0-1.26365.3
+ 5.10.0-1.26365.3
+ 5.10.0-1.26365.3
+ 5.10.0-1.26365.3
+ 5.10.0-1.26365.3
+ 5.10.0-1.26365.3
+ 5.10.0-1.26365.3
+ 5.10.0-1.26365.3
- 10.0.2
- 10.0.2
- 10.0.2
- 10.0.2
- 10.0.8
+ 10.0.8
+ 10.0.8
+ 10.0.8
+ 10.0.8
+ 10.0.10
@@ -55,7 +55,7 @@ This file should be imported by eng/Versions.props
$(MicrosoftCodeAnalysisCSharpPackageVersion)$(MicrosoftCodeAnalysisEditorFeaturesPackageVersion)$(MicrosoftCodeAnalysisEditorFeaturesTextPackageVersion)
- $(MicrosoftCodeAnalysisExternalAccessFSharpPackageVersion)
+ $(MicrosoftVisualStudioLanguageServicesExternalAccessPackageVersion)$(MicrosoftCodeAnalysisFeaturesPackageVersion)$(MicrosoftVisualStudioLanguageServicesPackageVersion)
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index 1bcf5a3b3b4..7432074974e 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -1,6 +1,6 @@
-
+ https://github.com/dotnet/msbuild
@@ -18,73 +18,73 @@
https://github.com/dotnet/msbuild746aeb090c9e2bcedc398751370da862014ebf7a
-
+ https://github.com/dotnet/roslyn
- 97ee762dcf4f7135ff591ebf24b6f1dab3fe0632
+ 3d32d464e2949f054086fbb5346e4beea0c6df56
-
+ https://github.com/dotnet/roslyn
- 97ee762dcf4f7135ff591ebf24b6f1dab3fe0632
+ 3d32d464e2949f054086fbb5346e4beea0c6df56
-
+ https://github.com/dotnet/roslyn
- 97ee762dcf4f7135ff591ebf24b6f1dab3fe0632
+ 3d32d464e2949f054086fbb5346e4beea0c6df56
-
+ https://github.com/dotnet/roslyn
- 97ee762dcf4f7135ff591ebf24b6f1dab3fe0632
+ 3d32d464e2949f054086fbb5346e4beea0c6df56
-
+ https://github.com/dotnet/roslyn
- 97ee762dcf4f7135ff591ebf24b6f1dab3fe0632
+ 3d32d464e2949f054086fbb5346e4beea0c6df56
-
+ https://github.com/dotnet/roslyn
- 97ee762dcf4f7135ff591ebf24b6f1dab3fe0632
+ 3d32d464e2949f054086fbb5346e4beea0c6df56
-
+ https://github.com/dotnet/roslyn
- 97ee762dcf4f7135ff591ebf24b6f1dab3fe0632
+ 3d32d464e2949f054086fbb5346e4beea0c6df56
-
+ https://github.com/dotnet/roslyn
- 97ee762dcf4f7135ff591ebf24b6f1dab3fe0632
+ 3d32d464e2949f054086fbb5346e4beea0c6df56
-
+ https://github.com/dotnet/runtime
-
+ https://github.com/dotnet/runtime
-
+ https://github.com/dotnet/runtime
-
+ https://github.com/dotnet/runtime
-
+ https://github.com/dotnet/runtime
-
+ https://github.com/dotnet/arcade
- c5d54a9de6e0e20a85e37fa3576a37235276772b
+ 0e35127eec8820435a0de7f2349ae2b99eeb9c7bhttps://dev.azure.com/dnceng/internal/_git/dotnet-optimization
diff --git a/eng/Versions.props b/eng/Versions.props
index 6a8cb85dd4e..f2902fb245e 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -28,7 +28,6 @@
1
- $(FSMajorVersion).$(FSMinorVersion)$(FSMajorVersion).$(FSMinorVersion).$(FSBuildVersion)$(FSMajorVersion).$(FSMinorVersion).$(FSBuildVersion)$(FSMajorVersion).$(FSMinorVersion).0.0
@@ -38,7 +37,7 @@
43
- 12
+ 13$(FSBuildVersion)$(FSRevisionVersion)$(FCSMajorVersion).$(FCSMinorVersion).$(FCSBuildVersion)
@@ -89,7 +88,26 @@
4.6.14.6.36.1.2
-
+
+
+ $(SystemSecurityCryptographyXmlVersion)
+ $(SystemCollectionsImmutableVersion)
+ $(SystemReflectionMetadataVersion)
+
+
+
+
+ 10.0.9
+ $(SystemRuntimeCentralFloorVersion)
+ $(SystemRuntimeCentralFloorVersion)
+ $(SystemRuntimeCentralFloorVersion)
@@ -97,72 +115,30 @@
4.7.0
- 1.6.0
-
- 18.0.404-preview
- 18.0.2188-preview.1
- 18.0.1237-pre
- 18.0.2077-preview.1
- 18.0.5
-
-
- 2.0.28
-
- $(MicrosoftVisualStudioShellPackagesVersion)
- $(VisualStudioShellProjectsPackages)
- $(MicrosoftVisualStudioShellPackagesVersion)
- $(MicrosoftVisualStudioShellPackagesVersion)
- $(MicrosoftVisualStudioShellPackagesVersion)
- $(MicrosoftVisualStudioShellPackagesVersion)
- $(MicrosoftVisualStudioShellPackagesVersion)
- $(MicrosoftVisualStudioShellPackagesVersion)
- $(VisualStudioShellProjectsPackages)
- $(MicrosoftVisualStudioShellPackagesVersion)
- 10.0.30319
- 11.0.50727
- 15.0.25123-Dev15Preview
-
-
- $(VisualStudioEditorPackagesVersion)
- $(VisualStudioEditorPackagesVersion)
- $(VisualStudioEditorPackagesVersion)
- $(VisualStudioEditorPackagesVersion)
- $(VisualStudioEditorPackagesVersion)
- 17.14.0
+
+
+ 18.9.1230.1.800-beta
- $(MicrosoftVisualStudioExtensibilityTestingVersion)
-
- $(MicrosoftVisualStudioThreadingPackagesVersion)
+
+ 17.14.2120
-
- $(VisualStudioProjectSystemPackagesVersion)
- 2.3.6152103
+
+ 4.3.0-1.22220.8
+ 5.0.0-preview.7.20364.11
+ 5.0.0-preview.7.20364.11
-
- 17.14.2120
- 17.0.0
-
-
- 0.2.0
- 1.0.0
- 1.1.87
-
0.13.10
- 2.16.6
- 4.3.0-1.22220.8
-
- 5.0.0-preview.7.20364.11
- 5.0.0-preview.7.20364.11
- 17.14.1
+ 18.0.12.0.2
- 13.0.43.2.2
- 3.2.28.0.0
-
diff --git a/eng/common/AGENTS.md b/eng/common/AGENTS.md
new file mode 100644
index 00000000000..a5ed8f72926
--- /dev/null
+++ b/eng/common/AGENTS.md
@@ -0,0 +1,5 @@
+# `eng/common`
+
+Files under `eng/common` come from [Arcade](https://github.com/dotnet/arcade).
+Edits in `eng/common` will be overwritten by automation unless the changes are made directly in the Arcade repository.
+For more information, see the [Arcade documentation](https://github.com/dotnet/arcade/tree/main/Documentation).
diff --git a/eng/common/SetupNugetSources.ps1 b/eng/common/SetupNugetSources.ps1
index 65ed3a8adef..b3bddff355e 100644
--- a/eng/common/SetupNugetSources.ps1
+++ b/eng/common/SetupNugetSources.ps1
@@ -1,7 +1,6 @@
# This script adds internal feeds required to build commits that depend on internal package sources. For instance,
-# dotnet6-internal would be added automatically if dotnet6 was found in the nuget.config file. Similarly,
-# dotnet-eng-internal and dotnet-tools-internal are added if dotnet-eng and dotnet-tools are present.
-# In addition, this script also enables disabled internal Maestro (darc-int*) feeds.
+# dotnet6-internal would be added automatically if dotnet6 was found in the nuget.config file. In addition also enables
+# disabled internal Maestro (darc-int*) feeds.
#
# Optionally, this script also adds a credential entry for each of the internal feeds if supplied.
#
@@ -14,7 +13,11 @@
# filePath: $(System.DefaultWorkingDirectory)/eng/common/SetupNugetSources.ps1
# arguments: -ConfigFile $(System.DefaultWorkingDirectory)/NuGet.config -Password $Env:Token
# env:
-# Token: $(dn-bot-dnceng-artifact-feeds-rw)
+# Token: $(InternalFeedToken)
+#
+# Note: This logic is abstracted into enable-internal-sources.yml, which uses
+# NuGetAuthenticate or a WIF-backed service connection. Prefer that template
+# over calling this script directly.
#
# Note that the NuGetAuthenticate task should be called after SetupNugetSources.
# This ensures that:
@@ -33,6 +36,11 @@ $ErrorActionPreference = "Stop"
Set-StrictMode -Version 2.0
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+# This script only consumes helper functions from tools.ps1 to configure NuGet feeds.
+# Skip importing configure-toolset.ps1 so that repo-specific toolset setup (e.g. acquiring
+# a bootstrap SDK) is not triggered as a side effect of feed configuration.
+$disableConfigureToolsetImport = $true
+
. $PSScriptRoot\tools.ps1
# Adds or enables the package source with the given name
@@ -174,16 +182,4 @@ foreach ($dotnetVersion in $dotnetVersions) {
}
}
-# Check for dotnet-eng and add dotnet-eng-internal if present
-$dotnetEngSource = $sources.SelectSingleNode("add[@key='dotnet-eng']")
-if ($dotnetEngSource -ne $null) {
- AddOrEnablePackageSource -Sources $sources -DisabledPackageSources $disabledSources -SourceName "dotnet-eng-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet-eng-internal/nuget/$feedSuffix" -Creds $creds -Username $userName -pwd $Password
-}
-
-# Check for dotnet-tools and add dotnet-tools-internal if present
-$dotnetToolsSource = $sources.SelectSingleNode("add[@key='dotnet-tools']")
-if ($dotnetToolsSource -ne $null) {
- AddOrEnablePackageSource -Sources $sources -DisabledPackageSources $disabledSources -SourceName "dotnet-tools-internal" -SourceEndPoint "https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet-tools-internal/nuget/$feedSuffix" -Creds $creds -Username $userName -pwd $Password
-}
-
$doc.Save($filename)
diff --git a/eng/common/SetupNugetSources.sh b/eng/common/SetupNugetSources.sh
index b2163abbe71..67e7e0942ca 100755
--- a/eng/common/SetupNugetSources.sh
+++ b/eng/common/SetupNugetSources.sh
@@ -1,9 +1,8 @@
#!/usr/bin/env bash
# This script adds internal feeds required to build commits that depend on internal package sources. For instance,
-# dotnet6-internal would be added automatically if dotnet6 was found in the nuget.config file. Similarly,
-# dotnet-eng-internal and dotnet-tools-internal are added if dotnet-eng and dotnet-tools are present.
-# In addition, this script also enables disabled internal Maestro (darc-int*) feeds.
+# dotnet6-internal would be added automatically if dotnet6 was found in the nuget.config file. In addition also enables
+# disabled internal Maestro (darc-int*) feeds.
#
# Optionally, this script also adds a credential entry for each of the internal feeds if supplied.
#
@@ -41,6 +40,11 @@ while [[ -h "$source" ]]; do
done
scriptroot="$( cd -P "$( dirname "$source" )" && pwd )"
+# This script only consumes helper functions from tools.sh to configure NuGet feeds.
+# Skip importing configure-toolset.sh so that repo-specific toolset setup (e.g. acquiring
+# a bootstrap SDK) is not triggered as a side effect of feed configuration.
+disable_configure_toolset_import=1
+
. "$scriptroot/tools.sh"
if [ ! -f "$ConfigFile" ]; then
@@ -174,18 +178,6 @@ for DotNetVersion in ${DotNetVersions[@]} ; do
fi
done
-# Check for dotnet-eng and add dotnet-eng-internal if present
-grep -i " /dev/null
-if [ "$?" == "0" ]; then
- AddOrEnablePackageSource "dotnet-eng-internal" "https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet-eng-internal/nuget/$FeedSuffix"
-fi
-
-# Check for dotnet-tools and add dotnet-tools-internal if present
-grep -i " /dev/null
-if [ "$?" == "0" ]; then
- AddOrEnablePackageSource "dotnet-tools-internal" "https://pkgs.dev.azure.com/dnceng/internal/_packaging/dotnet-tools-internal/nuget/$FeedSuffix"
-fi
-
# I want things split line by line
PrevIFS=$IFS
IFS=$'\n'
diff --git a/eng/common/build.ps1 b/eng/common/build.ps1
index 8cfee107e7a..dd84699f500 100644
--- a/eng/common/build.ps1
+++ b/eng/common/build.ps1
@@ -6,6 +6,7 @@ Param(
[string][Alias('v')]$verbosity = "minimal",
[string] $msbuildEngine = $null,
[bool] $warnAsError = $true,
+ [string] $warnNotAsError = '',
[bool] $nodeReuse = $true,
[switch] $buildCheck = $false,
[switch][Alias('r')]$restore,
@@ -22,7 +23,9 @@ Param(
[switch] $clean,
[switch][Alias('pb')]$productBuild,
[switch]$fromVMR,
+ [switch]$disablePipelineSetResult,
[switch][Alias('bl')]$binaryLog,
+ [string][Alias('bln')]$binaryLogName = '',
[switch][Alias('nobl')]$excludeCIBinarylog,
[switch] $ci,
[switch] $prepareMachine,
@@ -45,6 +48,7 @@ function Print-Usage() {
Write-Host " -platform Platform configuration: 'x86', 'x64' or any valid Platform value to pass to msbuild"
Write-Host " -verbosity Msbuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] (short: -v)"
Write-Host " -binaryLog Output binary log (short: -bl)"
+ Write-Host " -binaryLogName Binary log file name or path; implies -binaryLog (short: -bln)"
Write-Host " -help Print help and exit"
Write-Host ""
@@ -70,12 +74,14 @@ function Print-Usage() {
Write-Host " -excludeCIBinarylog Don't output binary log (short: -nobl)"
Write-Host " -prepareMachine Prepare machine for CI run, clean up processes after build"
Write-Host " -warnAsError Sets warnaserror msbuild parameter ('true' or 'false')"
+ Write-Host " -warnNotAsError Sets a semi-colon delimited list of warning codes that should not be treated as errors"
Write-Host " -msbuildEngine Msbuild engine to use to run build ('dotnet', 'vs', or unspecified)."
Write-Host " -excludePrereleaseVS Set to exclude build engines in prerelease versions of Visual Studio"
Write-Host " -nativeToolsOnMachine Sets the native tools on machine environment variable (indicating that the script should use native tools on machine)"
Write-Host " -nodeReuse Sets nodereuse msbuild parameter ('true' or 'false')"
Write-Host " -buildCheck Sets /check msbuild parameter"
Write-Host " -fromVMR Set when building from within the VMR"
+ Write-Host " -disablePipelineSetResult Set to disable masking the actual exit code in the pipeline when the build fails"
Write-Host ""
Write-Host "Command line arguments not listed above are passed thru to msbuild."
@@ -100,7 +106,19 @@ function Build {
$toolsetBuildProj = InitializeToolset
InitializeCustomToolset
- $bl = if ($binaryLog) { '/bl:' + (Join-Path $LogDir 'Build.binlog') } else { '' }
+ $bl = ''
+ if ($binaryLog) {
+ $binaryLogPath = if ([string]::IsNullOrEmpty($binaryLogName)) {
+ Join-Path $LogDir 'Build.binlog'
+ } elseif ([System.IO.Path]::IsPathRooted($binaryLogName)) {
+ $binaryLogName
+ } else {
+ Join-Path $LogDir $binaryLogName
+ }
+
+ Create-Directory (Split-Path -Parent $binaryLogPath)
+ $bl = '/bl:' + $binaryLogPath
+ }
$platformArg = if ($platform) { "/p:Platform=$platform" } else { '' }
$check = if ($buildCheck) { '/check' } else { '' }
@@ -157,7 +175,15 @@ try {
if (-not $excludeCIBinarylog) {
$binaryLog = $true
}
- $nodeReuse = $false
+ # Disable node reuse on CI unless explicitly opted in via MSBUILD_NODEREUSE_ENABLED.
+ # Internal testing only; this env var will be replaced with a switch (https://github.com/dotnet/arcade/issues/17013) and must not be depended on.
+ if ($env:MSBUILD_NODEREUSE_ENABLED -ne "1") {
+ $nodeReuse = $false
+ }
+ }
+
+ if (-not [string]::IsNullOrEmpty($binaryLogName)) {
+ $binaryLog = $true
}
if ($nativeToolsOnMachine) {
diff --git a/eng/common/build.sh b/eng/common/build.sh
index 9767bb411a4..e37edd6cff3 100755
--- a/eng/common/build.sh
+++ b/eng/common/build.sh
@@ -13,6 +13,7 @@ usage()
echo " --configuration Build configuration: 'Debug' or 'Release' (short: -c)"
echo " --verbosity Msbuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic] (short: -v)"
echo " --binaryLog Create MSBuild binary log (short: -bl)"
+ echo " --binaryLogName Binary log file name or path; implies --binaryLog (short: -bln)"
echo " --help Print help and exit (short: -h)"
echo ""
@@ -39,11 +40,14 @@ usage()
echo " --projects Project or solution file(s) to build"
echo " --ci Set when running on CI server"
echo " --excludeCIBinarylog Don't output binary log (short: -nobl)"
+ echo " --pipelinesLog Promote msbuild errors/warnings to Azure Pipelines timeline issues; defaults to on in CI (short: -pl)"
echo " --prepareMachine Prepare machine for CI run, clean up processes after build"
echo " --nodeReuse Sets nodereuse msbuild parameter ('true' or 'false')"
echo " --warnAsError Sets warnaserror msbuild parameter ('true' or 'false')"
+ echo " --warnNotAsError Sets a semi-colon delimited list of warning codes that should not be treated as errors"
echo " --buildCheck Sets /check msbuild parameter"
echo " --fromVMR Set when building from within the VMR"
+ echo " --disablePipelineSetResult Set to disable masking the actual exit code in the pipeline when the build fails"
echo ""
echo "Command line arguments not listed above are passed thru to msbuild."
echo "Arguments can also be passed in with a single hyphen."
@@ -66,6 +70,7 @@ build=false
source_build=false
product_build=false
from_vmr=false
+disable_pipeline_set_result=false
rebuild=false
test=false
integration_test=false
@@ -78,9 +83,11 @@ ci=false
clean=false
warn_as_error=true
+warn_not_as_error=''
node_reuse=true
build_check=false
binary_log=false
+binary_log_name=''
exclude_ci_binary_log=false
pipelines_log=false
@@ -92,7 +99,7 @@ runtime_source_feed=''
runtime_source_feed_key=''
properties=()
-while [[ $# > 0 ]]; do
+while [[ $# -gt 0 ]]; do
opt="$(echo "${1/#--/-}" | tr "[:upper:]" "[:lower:]")"
case "$opt" in
-help|-h)
@@ -113,6 +120,11 @@ while [[ $# > 0 ]]; do
-binarylog|-bl)
binary_log=true
;;
+ -binarylogname|-bln)
+ binary_log=true
+ binary_log_name=$2
+ shift
+ ;;
-excludecibinarylog|-nobl)
exclude_ci_binary_log=true
;;
@@ -147,6 +159,9 @@ while [[ $# > 0 ]]; do
-fromvmr|-from-vmr)
from_vmr=true
;;
+ -disablepipelinesetresult|-disable-pipeline-set-result)
+ disable_pipeline_set_result=true
+ ;;
-test|-t)
test=true
;;
@@ -176,6 +191,10 @@ while [[ $# > 0 ]]; do
warn_as_error=$2
shift
;;
+ -warnnotaserror)
+ warn_not_as_error=$2
+ shift
+ ;;
-nodereuse)
node_reuse=$2
shift
@@ -205,7 +224,11 @@ fi
if [[ "$ci" == true ]]; then
pipelines_log=true
- node_reuse=false
+ # Disable node reuse on CI unless explicitly opted in via MSBUILD_NODEREUSE_ENABLED.
+ # Internal testing only; this env var will be replaced with a switch (https://github.com/dotnet/arcade/issues/17013) and must not be depended on.
+ if [[ "${MSBUILD_NODEREUSE_ENABLED:-}" != "1" ]]; then
+ node_reuse=false
+ fi
if [[ "$exclude_ci_binary_log" == false ]]; then
binary_log=true
fi
@@ -231,7 +254,17 @@ function Build {
local bl=""
if [[ "$binary_log" == true ]]; then
- bl="/bl:\"$log_dir/Build.binlog\""
+ local binary_log_path=""
+ if [[ -z "$binary_log_name" ]]; then
+ binary_log_path="$log_dir/Build.binlog"
+ elif [[ "$binary_log_name" = /* ]]; then
+ binary_log_path="$binary_log_name"
+ else
+ binary_log_path="$log_dir/$binary_log_name"
+ fi
+
+ mkdir -p "$(dirname "$binary_log_path")"
+ bl="/bl:\"$binary_log_path\""
fi
local check=""
diff --git a/eng/common/core-templates/job/helix-job-monitor.yml b/eng/common/core-templates/job/helix-job-monitor.yml
new file mode 100644
index 00000000000..0da13cf69db
--- /dev/null
+++ b/eng/common/core-templates/job/helix-job-monitor.yml
@@ -0,0 +1,235 @@
+parameters:
+# Maximum run time of the monitor job in minutes. Also used for --max-wait-minutes.
+- name: timeoutInMinutes
+ type: number
+ default: 360
+
+# Owner segment of the source repository (e.g. 'dotnet' for 'dotnet/runtime') passed via --organization.
+# Defaults to the owner segment of BUILD_REPOSITORY_NAME when empty.
+- name: organization
+ type: string
+ default: ''
+
+# Name of the source repository (e.g. 'runtime' for 'dotnet/runtime') passed via --repository.
+# Defaults to the repo segment of BUILD_REPOSITORY_NAME when empty.
+- name: repository
+ type: string
+ default: ''
+
+# Optional dependency list for the generated job.
+- name: dependsOn
+ type: object
+ default: []
+
+# Optional condition for the generated job.
+- name: condition
+ type: string
+ default: ''
+
+# NuGet package id of the Helix job monitor tool.
+- name: toolPackageId
+ type: string
+ default: Microsoft.DotNet.Helix.JobMonitor
+
+# Console command exposed by the installed tool package.
+- name: toolCommand
+ type: string
+ default: dotnet-helix-job-monitor
+
+# Optional explicit tool version. Only honored when 'toolNupkgArtifactName' is set; in the
+# default code path the version is taken from the consuming repo's .config/dotnet-tools.json.
+- name: toolVersion
+ type: string
+ default: ''
+
+# Base URI for the Helix service (--helix-base-uri).
+- name: helixBaseUri
+ type: string
+ default: https://helix.dot.net/
+
+# Helix API access token forwarded to the tool via the HELIX_ACCESSTOKEN environment variable.
+- name: helixAccessToken
+ type: string
+ default: ''
+
+# Polling interval in seconds (--polling-interval-seconds).
+- name: pollingIntervalSeconds
+ type: number
+ default: 30
+
+# When 'true' (the default), Helix work items that exit 0 but have failed AzDO test results
+# are treated as failed: they count toward the monitor's exit code and are resubmitted by a
+# later invocation's retry pass. Set to 'false' to fall back to exit-code-only outcomes.
+# Forwarded as --fail-on-failed-tests.
+- name: failWorkItemsWithFailedTests
+ type: boolean
+ default: true
+
+# When true, test results are reported to Azure DevOps using the fully qualified test name
+# (Namespace.Type.Method) as the stable automatedTestName and the visible title is qualified as
+# well (--use-fully-qualified-test-name). Opt-in because it changes AzDO test identity and display;
+# primarily useful for frameworks like MSTest whose display name is only the method name.
+- name: useFullyQualifiedTestName
+ type: boolean
+ default: false
+
+# Advanced: optional pipeline artifact (produced earlier in this run) that contains the tool
+# nupkg. When set, the artifact is downloaded and the tool is installed from the nupkg into
+# a local tool-path; this bypasses the repo's .config/dotnet-tools.json manifest and is
+# primarily intended for the Arcade repository itself, where the Helix job monitor tool is
+# built in the same pipeline that runs this template.
+#
+# When this parameter is empty (the default), the consuming repository must declare the tool
+# in its .config/dotnet-tools.json manifest (alongside other local .NET tools); the template
+# will check out the repo and run 'dotnet tool restore' to install the version pinned there.
+- name: toolNupkgArtifactName
+ type: string
+ default: ''
+
+# Advanced: sub-path within the downloaded artifact where the tool nupkg is located. Defaults
+# to the standard Arcade non-shipping packages location for a Release build (relative to the
+# pipeline artifact root, which is itself the build's 'artifacts' directory).
+- name: toolNupkgArtifactSubPath
+ type: string
+ default: 'packages/Release/NonShipping'
+
+jobs:
+- job: HelixJobMonitor
+ displayName: Monitor Helix Jobs
+ timeoutInMinutes: ${{ parameters.timeoutInMinutes }}
+ ${{ if ne(length(parameters.dependsOn), 0) }}:
+ dependsOn: ${{ parameters.dependsOn }}
+ ${{ if ne(parameters.condition, '') }}:
+ condition: ${{ parameters.condition }}
+ pool:
+ ${{ if eq(variables['System.TeamProject'], 'public') }}:
+ name: $(DncEngPublicBuildPool)
+ demands: ImageOverride -equals build.azurelinux.3.amd64.open
+ ${{ else }}:
+ name: $(DncEngInternalBuildPool)
+ demands: ImageOverride -equals build.azurelinux.3.amd64
+ steps:
+ - checkout: self
+ fetchDepth: 1
+
+ - ${{ if ne(parameters.toolNupkgArtifactName, '') }}:
+ - task: DownloadPipelineArtifact@2
+ displayName: Download Helix Job Monitor artifact
+ inputs:
+ buildType: current
+ artifactName: ${{ parameters.toolNupkgArtifactName }}
+ itemPattern: '${{ parameters.toolNupkgArtifactSubPath }}/${{ parameters.toolPackageId }}.*.nupkg'
+ targetPath: $(Agent.TempDirectory)/helix-job-monitor-nupkg
+
+ - bash: |
+ set -euo pipefail
+
+ toolPath="$AGENT_TEMPDIRECTORY/helix-job-monitor-tool"
+ mkdir -p "$toolPath"
+
+ packageId='${{ parameters.toolPackageId }}'
+ toolVersion='${{ parameters.toolVersion }}'
+ nupkgArtifactSubPath='${{ parameters.toolNupkgArtifactSubPath }}'
+ nupkgDir="$AGENT_TEMPDIRECTORY/helix-job-monitor-nupkg/$nupkgArtifactSubPath"
+
+ if [ ! -d "$nupkgDir" ]; then
+ echo "Expected nupkg directory '$nupkgDir' was not produced by the artifact download." >&2
+ exit 1
+ fi
+
+ nupkg=$(find "$nupkgDir" -maxdepth 1 -type f -name "$packageId.*.nupkg" | head -n 1)
+ if [ -z "$nupkg" ]; then
+ echo "No '$packageId.*.nupkg' found in '$nupkgDir'." >&2
+ exit 1
+ fi
+
+ # Derive the version from the nupkg filename so the local package is selected
+ # deterministically instead of resolving against any other configured feed.
+ nupkgBase=$(basename "$nupkg" .nupkg)
+ derivedVersion="${nupkgBase#${packageId}.}"
+ if [ -z "$toolVersion" ]; then
+ toolVersion="$derivedVersion"
+ fi
+
+ echo "Using locally built '$packageId' version '$toolVersion' from '$nupkgDir'."
+
+ # Create a minimal NuGet.config that only references the local nupkg directory.
+ # This avoids conflicts with the repo's package source mapping which blocks --add-source.
+ toolNugetConfig="$AGENT_TEMPDIRECTORY/helix-job-monitor-nuget.config"
+ printf '\n\n \n \n \n \n\n' "$nupkgDir" > "$toolNugetConfig"
+
+ pushd "$(Build.SourcesDirectory)" > /dev/null
+ ./eng/common/dotnet.sh tool install \
+ --tool-path "$toolPath" "$packageId" \
+ --version "$toolVersion" \
+ --configfile "$toolNugetConfig"
+
+ # Locate the tool DLL so the run step can invoke it via ./eng/common/dotnet.sh exec.
+ toolDll=$(find "$toolPath/.store" -path '*/tools/*/any/*.deps.json' -type f | head -n 1)
+ toolDll="${toolDll%.deps.json}.dll"
+ if [ ! -f "$toolDll" ]; then
+ echo "Could not find tool DLL in '$toolPath/.store'." >&2
+ exit 1
+ fi
+
+ echo "Tool DLL: $toolDll"
+ echo "##vso[task.setvariable variable=HelixJobMonitorDll]$toolDll"
+ displayName: Install Helix Job Monitor
+
+ - ${{ else }}:
+ - bash: ./eng/common/dotnet.sh tool restore
+ displayName: Restore Helix Job Monitor
+
+ - bash: |
+ set -euo pipefail
+
+ toolArgs=(
+ --helix-base-uri '${{ parameters.helixBaseUri }}'
+ --polling-interval-seconds '${{ parameters.pollingIntervalSeconds }}'
+ --fail-on-failed-tests '${{ parameters.failWorkItemsWithFailedTests }}'
+ --use-fully-qualified-test-name '${{ parameters.useFullyQualifiedTestName }}'
+ --max-wait-minutes "$((${{ parameters.timeoutInMinutes }} - 5))" # Set the tool's timeout slightly lower than the Azure DevOps job timeout to allow it to exit gracefully.
+ --stage-name '$(System.StageName)'
+ )
+
+ organization='${{ parameters.organization }}'
+ repository='${{ parameters.repository }}'
+
+ # Fall back to Azure DevOps-provided environment variables when the caller did not
+ # supply organization / repository explicitly. BUILD_REPOSITORY_NAME is typically
+ # 'owner/repo' for GitHub-backed builds.
+ if [ -z "$organization" ] || [ -z "$repository" ]; then
+ buildRepoName="${BUILD_REPOSITORY_NAME:-}"
+ if [ -n "$buildRepoName" ] && [[ "$buildRepoName" == */* ]]; then
+ repoOwner="${buildRepoName%%/*}"
+ repoName="${buildRepoName#*/}"
+ if [ -z "$organization" ]; then organization="$repoOwner"; fi
+ if [ -z "$repository" ]; then repository="$repoName"; fi
+ fi
+ fi
+
+ if [ -n "$organization" ]; then toolArgs+=( --organization "$organization" ); fi
+ if [ -n "$repository" ]; then toolArgs+=( --repository "$repository" ); fi
+
+ # Build.Reason and Build.SourceBranch are required to derive the Helix source filter
+ # the same way the Helix SDK submitter does (PR -> 'pr', internal -> 'official',
+ # otherwise -> 'ci'). Without these, manually-queued / scheduled / CI builds would
+ # be looked up under the wrong source prefix and find zero jobs.
+ toolArgs+=( --build-reason "$(Build.Reason)" )
+ toolArgs+=( --source-branch "$(Build.SourceBranch)" )
+
+ if [ -n '${{ parameters.toolNupkgArtifactName }}' ]; then
+ # Tool was installed from a local nupkg; run the DLL via the repo-local dotnet.
+ export DOTNET_ROOT="$(Build.SourcesDirectory)/.dotnet"
+ ./eng/common/dotnet.sh exec "$(HelixJobMonitorDll)" "${toolArgs[@]}"
+ else
+ # Tool was restored from the local .config/dotnet-tools.json manifest; invoke it
+ # through the manifest from the repo root.
+ pushd "$BUILD_SOURCESDIRECTORY" > /dev/null
+ trap 'popd > /dev/null' EXIT
+ ./eng/common/dotnet.sh tool run '${{ parameters.toolCommand }}' -- "${toolArgs[@]}"
+ fi
+ displayName: Monitor Helix Jobs
+ env:
+ SYSTEM_ACCESSTOKEN: $(System.AccessToken)
+ HELIX_ACCESSTOKEN: ${{ parameters.helixAccessToken }}
diff --git a/eng/common/core-templates/job/job.yml b/eng/common/core-templates/job/job.yml
index eaed6d87e65..cb60f529784 100644
--- a/eng/common/core-templates/job/job.yml
+++ b/eng/common/core-templates/job/job.yml
@@ -19,6 +19,8 @@ parameters:
# publishing defaults
artifacts: ''
enableMicrobuild: false
+ enablePreviewMicrobuild: false
+ microbuildPluginVersion: 'latest'
enableMicrobuildForMacAndLinux: false
microbuildUseESRP: true
enablePublishBuildArtifacts: false
@@ -71,6 +73,14 @@ jobs:
templateContext: ${{ parameters.templateContext }}
variables:
+ - name: AllowPtrToDetectTestRunRetryFiles
+ value: true
+ # Component Governance detection and CodeQL are not run in the public project
+ - ${{ if eq(variables['System.TeamProject'], 'public') }}:
+ - name: skipComponentGovernanceDetection
+ value: true
+ - name: Codeql.SkipTaskAutoInjection
+ value: true
- ${{ if ne(parameters.enableTelemetry, 'false') }}:
- name: DOTNET_CLI_TELEMETRY_PROFILE
value: '$(Build.Repository.Uri)'
@@ -128,6 +138,8 @@ jobs:
- template: /eng/common/core-templates/steps/install-microbuild.yml
parameters:
enableMicrobuild: ${{ parameters.enableMicrobuild }}
+ enablePreviewMicrobuild: ${{ parameters.enablePreviewMicrobuild }}
+ microbuildPluginVersion: ${{ parameters.microbuildPluginVersion }}
enableMicrobuildForMacAndLinux: ${{ parameters.enableMicrobuildForMacAndLinux }}
microbuildUseESRP: ${{ parameters.microbuildUseESRP }}
continueOnError: ${{ parameters.continueOnError }}
@@ -150,6 +162,8 @@ jobs:
- template: /eng/common/core-templates/steps/cleanup-microbuild.yml
parameters:
enableMicrobuild: ${{ parameters.enableMicrobuild }}
+ enablePreviewMicrobuild: ${{ parameters.enablePreviewMicrobuild }}
+ microbuildPluginVersion: ${{ parameters.microbuildPluginVersion }}
enableMicrobuildForMacAndLinux: ${{ parameters.enableMicrobuildForMacAndLinux }}
continueOnError: ${{ parameters.continueOnError }}
diff --git a/eng/common/core-templates/job/onelocbuild.yml b/eng/common/core-templates/job/onelocbuild.yml
index 12d7e55a94b..2816d2905a0 100644
--- a/eng/common/core-templates/job/onelocbuild.yml
+++ b/eng/common/core-templates/job/onelocbuild.yml
@@ -28,6 +28,7 @@ parameters:
GitHubOrg: dotnet
MirrorRepo: ''
MirrorBranch: main
+ xLocCustomPowerShellScript: ''
condition: ''
JobNameSuffix: ''
is1ESPipeline: ''
@@ -115,6 +116,8 @@ jobs:
gitHubOrganization: ${{ parameters.GitHubOrg }}
mirrorRepo: ${{ parameters.MirrorRepo }}
mirrorBranch: ${{ parameters.MirrorBranch }}
+ ${{ if ne(parameters.xLocCustomPowerShellScript, '') }}:
+ xLocCustomPowerShellScript: ${{ parameters.xLocCustomPowerShellScript }}
condition: ${{ parameters.condition }}
# Copy the locProject.json to the root of the Loc directory, then publish a pipeline artifact
diff --git a/eng/common/core-templates/job/renovate.yml b/eng/common/core-templates/job/renovate.yml
new file mode 100644
index 00000000000..ff86c80b468
--- /dev/null
+++ b/eng/common/core-templates/job/renovate.yml
@@ -0,0 +1,196 @@
+# --------------------------------------------------------------------------------------
+# Renovate Bot Job Template
+# --------------------------------------------------------------------------------------
+# This Azure DevOps pipeline job template runs Renovate (https://docs.renovatebot.com/)
+# to automatically update dependencies in a GitHub repository.
+#
+# Renovate scans the repository for dependency files and creates pull requests to update
+# outdated dependencies based on the configuration specified in the renovateConfigPath
+# parameter.
+#
+# Usage:
+# For each product repo wanting to make use of Renovate, this template is called from
+# an internal Azure DevOps pipeline, typically with a schedule trigger, to check for
+# and propose dependency updates.
+#
+# For more info, see https://github.com/dotnet/arcade/blob/main/Documentation/Renovate.md
+# --------------------------------------------------------------------------------------
+
+parameters:
+
+# Path to the Renovate configuration file within the repository.
+- name: renovateConfigPath
+ type: string
+ default: 'eng/renovate.json'
+
+# GitHub repository to run Renovate against, in the format 'owner/repo'.
+# This could technically be any repo but convention is to target the same
+# repo that contains the calling pipeline. The Renovate config file would
+# be co-located with the pipeline's repo and, in most cases, the config
+# file is specific to the repo being targeted.
+- name: gitHubRepo
+ type: string
+
+# List of base branches to target for Renovate PRs.
+# NOTE: The Renovate configuration file is always read from the branch where the
+# pipeline is run, NOT from the target branches specified here. If you need different
+# configurations for different branches, run the pipeline from each branch separately.
+- name: baseBranches
+ type: object
+ default:
+ - main
+
+# When true, Renovate will run in dry run mode, which previews changes without creating PRs.
+# See the 'Run Renovate' step log output for details of what would have been changed.
+- name: dryRun
+ type: boolean
+ default: false
+
+# By default, Renovate will not recreate a PR for a given dependency/version pair that was
+# previously closed. This allows opting in to always recreating PRs even if they were
+# previously closed.
+- name: forceRecreatePR
+ type: boolean
+ default: false
+
+# Name of the arcade repository resource in the pipeline.
+# This allows repos which haven't been onboarded to Arcade to still use this
+# template by checking out the repo as a resource with a custom name and pointing
+# this parameter to it.
+- name: arcadeRepoResource
+ type: string
+ default: self
+
+# Directory name for the self repo under $(Build.SourcesDirectory) in multi-checkout.
+# In multi-checkout (when arcadeRepoResource != 'self'), Azure DevOps checks out the
+# self repo to $(Build.SourcesDirectory)/. Set this to match the auto-generated
+# directory name. Using the auto-generated name is necessary rather than explicitly
+# defining a checkout path because container jobs expect repos to live under the agent's
+# workspace ($(Pipeline.Workspace)). On some self-hosted setups the host path
+# (e.g., /mnt/vss/_work) differs from the container path (e.g., /__w), and a custom checkout
+# path can fail validation. Using the default checkout location keeps the paths consistent
+# and avoids this issue.
+- name: selfRepoName
+ type: string
+ default: ''
+- name: arcadeRepoName
+ type: string
+ default: ''
+
+# Pool configuration for the job.
+- name: pool
+ type: object
+ default:
+ name: NetCore1ESPool-Internal
+ image: build.azurelinux.3.amd64
+ os: linux
+
+jobs:
+- job: Renovate
+ displayName: Run Renovate
+ container: RenovateContainer
+ variables:
+ - group: dotnet-renovate-bot
+ # The Renovate version is automatically updated by https://github.com/dotnet/arcade/blob/main/azure-pipelines-renovate.yml.
+ # Changing the variable name here would require updating the name in https://github.com/dotnet/arcade/blob/main/eng/renovate.json as well.
+ - name: renovateVersion
+ value: '42'
+ readonly: true
+ - name: renovateLogFilePath
+ value: '$(Build.ArtifactStagingDirectory)/renovate.json'
+ readonly: true
+ - name: dryRunArg
+ readonly: true
+ ${{ if eq(parameters.dryRun, true) }}:
+ value: 'full'
+ ${{ else }}:
+ value: ''
+ - name: recreateWhenArg
+ readonly: true
+ ${{ if eq(parameters.forceRecreatePR, true) }}:
+ value: 'always'
+ ${{ else }}:
+ value: ''
+ # In multi-checkout (without custom paths), Azure DevOps places each repo under
+ # $(Build.SourcesDirectory)/. selfRepoName must be provided in that case.
+ - name: selfRepoPath
+ readonly: true
+ ${{ if eq(parameters.arcadeRepoResource, 'self') }}:
+ value: '$(Build.SourcesDirectory)'
+ ${{ else }}:
+ value: '$(Build.SourcesDirectory)/${{ parameters.selfRepoName }}'
+ - name: arcadeRepoPath
+ readonly: true
+ ${{ if eq(parameters.arcadeRepoResource, 'self') }}:
+ value: '$(Build.SourcesDirectory)'
+ ${{ else }}:
+ value: '$(Build.SourcesDirectory)/${{ parameters.arcadeRepoName }}'
+ pool: ${{ parameters.pool }}
+
+ templateContext:
+ outputParentDirectory: $(Build.ArtifactStagingDirectory)
+ outputs:
+ - output: pipelineArtifact
+ displayName: Publish Renovate Log
+ condition: succeededOrFailed()
+ targetPath: $(Build.ArtifactStagingDirectory)
+ artifactName: $(Agent.JobName)_Logs_Attempt$(System.JobAttempt)
+ isProduction: false # logs are non-production artifacts
+
+ steps:
+ - checkout: self
+ fetchDepth: 1
+
+ - ${{ if ne(parameters.arcadeRepoResource, 'self') }}:
+ - checkout: ${{ parameters.arcadeRepoResource }}
+ fetchDepth: 1
+
+ - script: |
+ renovate-config-validator $(selfRepoPath)/${{parameters.renovateConfigPath}} 2>&1 | tee /tmp/renovate-config-validator.out
+ validatorExit=${PIPESTATUS[0]}
+ if grep -q '^ WARN:' /tmp/renovate-config-validator.out; then
+ echo "##vso[task.logissue type=warning]Renovate config validator produced warnings."
+ echo "##vso[task.complete result=SucceededWithIssues]"
+ fi
+ exit $validatorExit
+ displayName: Validate Renovate config
+ env:
+ LOG_LEVEL: info
+ LOG_FILE_LEVEL: debug
+ LOG_FILE: $(Build.ArtifactStagingDirectory)/renovate-config-validator.json
+
+ - script: |
+ . $(arcadeRepoPath)/eng/common/renovate.env
+ renovate 2>&1 | tee /tmp/renovate.out
+ renovateExit=${PIPESTATUS[0]}
+ if grep -q '^ WARN:' /tmp/renovate.out; then
+ echo "##vso[task.logissue type=warning]Renovate produced warnings."
+ echo "##vso[task.complete result=SucceededWithIssues]"
+ fi
+ exit $renovateExit
+ displayName: Run Renovate
+ env:
+ RENOVATE_FORK_TOKEN: $(BotAccount-dotnet-renovate-bot-PAT)
+ RENOVATE_TOKEN: $(BotAccount-dotnet-renovate-bot-PAT)
+ RENOVATE_REPOSITORIES: ${{parameters.gitHubRepo}}
+ RENOVATE_BASE_BRANCHES: ${{ convertToJson(parameters.baseBranches) }}
+ RENOVATE_DRY_RUN: $(dryRunArg)
+ RENOVATE_RECREATE_WHEN: $(recreateWhenArg)
+ LOG_LEVEL: info
+ LOG_FILE_LEVEL: debug
+ LOG_FILE: $(renovateLogFilePath)
+ RENOVATE_CONFIG_FILE: $(selfRepoPath)/${{parameters.renovateConfigPath}}
+
+ - script: |
+ echo "PRs created by Renovate:"
+ if [ -s "$(renovateLogFilePath)" ]; then
+ if ! jq -r 'select(.msg == "PR created" and .pr != null) | "https://github.com/\(.repository)/pull/\(.pr)"' "$(renovateLogFilePath)" | sort -u; then
+ echo "##vso[task.logissue type=warning]Failed to parse Renovate log file with jq."
+ echo "##vso[task.complete result=SucceededWithIssues]"
+ fi
+ else
+ echo "##vso[task.logissue type=warning]No Renovate log file found or file is empty."
+ echo "##vso[task.complete result=SucceededWithIssues]"
+ fi
+ displayName: List created PRs
+ condition: and(succeededOrFailed(), eq('${{ parameters.dryRun }}', false))
diff --git a/eng/common/core-templates/job/source-index-stage1.yml b/eng/common/core-templates/job/source-index-stage1.yml
index 76baf5c2725..bac6ac5faac 100644
--- a/eng/common/core-templates/job/source-index-stage1.yml
+++ b/eng/common/core-templates/job/source-index-stage1.yml
@@ -15,6 +15,8 @@ jobs:
variables:
- name: BinlogPath
value: ${{ parameters.binlogPath }}
+ - name: skipComponentGovernanceDetection
+ value: true
- template: /eng/common/core-templates/variables/pool-providers.yml
parameters:
is1ESPipeline: ${{ parameters.is1ESPipeline }}
@@ -25,10 +27,10 @@ jobs:
pool:
${{ if eq(variables['System.TeamProject'], 'public') }}:
name: $(DncEngPublicBuildPool)
- image: windows.vs2026preview.scout.amd64.open
+ image: windows.vs2026.amd64.open
${{ if eq(variables['System.TeamProject'], 'internal') }}:
name: $(DncEngInternalBuildPool)
- image: windows.vs2026preview.scout.amd64
+ image: windows.vs2026.amd64
steps:
- ${{ if eq(parameters.is1ESPipeline, '') }}:
diff --git a/eng/common/core-templates/jobs/codeql-build.yml b/eng/common/core-templates/jobs/codeql-build.yml
deleted file mode 100644
index dbc14ac580a..00000000000
--- a/eng/common/core-templates/jobs/codeql-build.yml
+++ /dev/null
@@ -1,32 +0,0 @@
-parameters:
- # See schema documentation in /Documentation/AzureDevOps/TemplateSchema.md
- continueOnError: false
- # Required: A collection of jobs to run - https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=vsts&tabs=schema#job
- jobs: []
- # Optional: if specified, restore and use this version of Guardian instead of the default.
- overrideGuardianVersion: ''
- is1ESPipeline: ''
-
-jobs:
-- template: /eng/common/core-templates/jobs/jobs.yml
- parameters:
- is1ESPipeline: ${{ parameters.is1ESPipeline }}
- enableMicrobuild: false
- enablePublishBuildArtifacts: false
- enablePublishTestResults: false
- enablePublishBuildAssets: false
- enableTelemetry: true
-
- variables:
- - group: Publish-Build-Assets
- # The Guardian version specified in 'eng/common/sdl/packages.config'. This value must be kept in
- # sync with the packages.config file.
- - name: DefaultGuardianVersion
- value: 0.109.0
- - name: GuardianPackagesConfigFile
- value: $(System.DefaultWorkingDirectory)\eng\common\sdl\packages.config
- - name: GuardianVersion
- value: ${{ coalesce(parameters.overrideGuardianVersion, '$(DefaultGuardianVersion)') }}
-
- jobs: ${{ parameters.jobs }}
-
diff --git a/eng/common/core-templates/post-build/common-variables.yml b/eng/common/core-templates/post-build/common-variables.yml
index d5627a994ae..db298ae16ba 100644
--- a/eng/common/core-templates/post-build/common-variables.yml
+++ b/eng/common/core-templates/post-build/common-variables.yml
@@ -11,8 +11,6 @@ variables:
- name: MaestroApiVersion
value: "2020-02-20"
- - name: SourceLinkCLIVersion
- value: 3.0.0
- name: SymbolToolVersion
value: 1.0.1
- name: BinlogToolVersion
diff --git a/eng/common/core-templates/stages/renovate.yml b/eng/common/core-templates/stages/renovate.yml
new file mode 100644
index 00000000000..edab2818258
--- /dev/null
+++ b/eng/common/core-templates/stages/renovate.yml
@@ -0,0 +1,111 @@
+# --------------------------------------------------------------------------------------
+# Renovate Pipeline Template
+# --------------------------------------------------------------------------------------
+# This template provides a complete reusable pipeline definition for running Renovate
+# in a 1ES Official pipeline. Pipelines can extend from this template and only need
+# to pass the Renovate job parameters.
+#
+# For more info, see https://github.com/dotnet/arcade/blob/main/Documentation/Renovate.md
+# --------------------------------------------------------------------------------------
+
+parameters:
+
+# Path to the Renovate configuration file within the repository.
+- name: renovateConfigPath
+ type: string
+ default: 'eng/renovate.json'
+
+# GitHub repository to run Renovate against, in the format 'owner/repo'.
+- name: gitHubRepo
+ type: string
+
+# List of base branches to target for Renovate PRs.
+- name: baseBranches
+ type: object
+ default:
+ - main
+
+# When true, Renovate will run in dry run mode.
+- name: dryRun
+ type: boolean
+ default: false
+
+# When true, Renovate will recreate PRs even if they were previously closed.
+- name: forceRecreatePR
+ type: boolean
+ default: false
+
+# Name of the arcade repository resource in the pipeline.
+# This allows repos which haven't been onboarded to Arcade to still use this
+# template by checking out the repo as a resource with a custom name and pointing
+# this parameter to it.
+- name: arcadeRepoResource
+ type: string
+ default: 'self'
+
+- name: selfRepoName
+ type: string
+ default: ''
+- name: arcadeRepoName
+ type: string
+ default: ''
+
+# Pool configuration for the pipeline.
+- name: pool
+ type: object
+ default:
+ name: NetCore1ESPool-Internal
+ image: build.azurelinux.3.amd64
+ os: linux
+
+# Renovate version used in the container image tag.
+- name: renovateVersion
+ default: 43
+ type: number
+
+# Pool configuration for SDL analysis.
+- name: sdlPool
+ type: object
+ default:
+ name: NetCore1ESPool-Internal
+ image: windows.vs2026.amd64
+ os: windows
+
+resources:
+ repositories:
+ - repository: 1ESPipelineTemplates
+ type: git
+ name: 1ESPipelineTemplates/1ESPipelineTemplates
+ ref: refs/tags/release
+
+extends:
+ template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates
+ parameters:
+ pool: ${{ parameters.pool }}
+ sdl:
+ sourceAnalysisPool: ${{ parameters.sdlPool }}
+ # When repos that aren't onboarded to Arcade use this template, they set the
+ # arcadeRepoResource parameter to point to their Arcade repo resource. In that case,
+ # Aracde will be excluded from SDL analysis.
+ ${{ if ne(parameters.arcadeRepoResource, 'self') }}:
+ sourceRepositoriesToScan:
+ exclude:
+ - repository: ${{ parameters.arcadeRepoResource }}
+ containers:
+ RenovateContainer:
+ image: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-renovate-${{ parameters.renovateVersion }}-amd64
+ stages:
+ - stage: Renovate
+ displayName: Run Renovate
+ jobs:
+ - template: /eng/common/core-templates/job/renovate.yml@${{ parameters.arcadeRepoResource }}
+ parameters:
+ renovateConfigPath: ${{ parameters.renovateConfigPath }}
+ gitHubRepo: ${{ parameters.gitHubRepo }}
+ baseBranches: ${{ parameters.baseBranches }}
+ dryRun: ${{ parameters.dryRun }}
+ forceRecreatePR: ${{ parameters.forceRecreatePR }}
+ pool: ${{ parameters.pool }}
+ arcadeRepoResource: ${{ parameters.arcadeRepoResource }}
+ selfRepoName: ${{ parameters.selfRepoName }}
+ arcadeRepoName: ${{ parameters.arcadeRepoName }}
diff --git a/eng/common/core-templates/steps/enable-internal-sources.yml b/eng/common/core-templates/steps/enable-internal-sources.yml
index 4085512b690..51af9a01709 100644
--- a/eng/common/core-templates/steps/enable-internal-sources.yml
+++ b/eng/common/core-templates/steps/enable-internal-sources.yml
@@ -15,32 +15,56 @@ steps:
- ${{ if ne(variables['System.TeamProject'], 'public') }}:
- ${{ if ne(parameters.legacyCredential, '') }}:
- task: PowerShell@2
+ condition: and(succeeded(), eq(variables['Agent.Os'], 'Windows_NT'))
displayName: Setup Internal Feeds
inputs:
filePath: $(System.DefaultWorkingDirectory)/eng/common/SetupNugetSources.ps1
arguments: -ConfigFile $(System.DefaultWorkingDirectory)/NuGet.config -Password $Env:Token
env:
Token: ${{ parameters.legacyCredential }}
+ - task: Bash@3
+ condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT'))
+ displayName: Setup Internal Feeds
+ inputs:
+ targetType: inline
+ script: |
+ "$(System.DefaultWorkingDirectory)/eng/common/SetupNugetSources.sh" "$(System.DefaultWorkingDirectory)/NuGet.config" "$Token"
+ env:
+ Token: ${{ parameters.legacyCredential }}
# If running on dnceng (internal project), just use the default behavior for NuGetAuthenticate.
# If running on DevDiv, NuGetAuthenticate is not really an option. It's scoped to a single feed, and we have many feeds that
# may be added. Instead, we'll use the traditional approach (add cred to nuget.config), but use an account token.
- ${{ else }}:
- ${{ if eq(variables['System.TeamProject'], 'internal') }}:
- task: PowerShell@2
+ condition: and(succeeded(), eq(variables['Agent.Os'], 'Windows_NT'))
displayName: Setup Internal Feeds
inputs:
filePath: $(System.DefaultWorkingDirectory)/eng/common/SetupNugetSources.ps1
arguments: -ConfigFile $(System.DefaultWorkingDirectory)/NuGet.config
+ - task: Bash@3
+ condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT'))
+ displayName: Setup Internal Feeds
+ inputs:
+ filePath: $(System.DefaultWorkingDirectory)/eng/common/SetupNugetSources.sh
+ arguments: $(System.DefaultWorkingDirectory)/NuGet.config
- ${{ else }}:
- template: /eng/common/templates/steps/get-federated-access-token.yml
parameters:
federatedServiceConnection: ${{ parameters.nugetFederatedServiceConnection }}
outputVariableName: 'dnceng-artifacts-feeds-read-access-token'
- task: PowerShell@2
+ condition: and(succeeded(), eq(variables['Agent.Os'], 'Windows_NT'))
displayName: Setup Internal Feeds
inputs:
filePath: $(System.DefaultWorkingDirectory)/eng/common/SetupNugetSources.ps1
arguments: -ConfigFile $(System.DefaultWorkingDirectory)/NuGet.config -Password $(dnceng-artifacts-feeds-read-access-token)
+ - task: Bash@3
+ condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT'))
+ displayName: Setup Internal Feeds
+ inputs:
+ filePath: $(System.DefaultWorkingDirectory)/eng/common/SetupNugetSources.sh
+ arguments: $(System.DefaultWorkingDirectory)/NuGet.config $(dnceng-artifacts-feeds-read-access-token)
# This is required in certain scenarios to install the ADO credential provider.
# It installed by default in some msbuild invocations (e.g. VS msbuild), but needs to be installed for others
# (e.g. dotnet msbuild).
diff --git a/eng/common/core-templates/steps/install-microbuild-impl.yml b/eng/common/core-templates/steps/install-microbuild-impl.yml
new file mode 100644
index 00000000000..da22beb3f60
--- /dev/null
+++ b/eng/common/core-templates/steps/install-microbuild-impl.yml
@@ -0,0 +1,34 @@
+parameters:
+ - name: microbuildTaskInputs
+ type: object
+ default: {}
+
+ - name: microbuildEnv
+ type: object
+ default: {}
+
+ - name: enablePreviewMicrobuild
+ type: boolean
+ default: false
+
+ - name: condition
+ type: string
+
+ - name: continueOnError
+ type: boolean
+
+steps:
+- ${{ if eq(parameters.enablePreviewMicrobuild, true) }}:
+ - task: MicroBuildSigningPluginPreview@4
+ displayName: Install Preview MicroBuild plugin
+ inputs: ${{ parameters.microbuildTaskInputs }}
+ env: ${{ parameters.microbuildEnv }}
+ continueOnError: ${{ parameters.continueOnError }}
+ condition: ${{ parameters.condition }}
+- ${{ else }}:
+ - task: MicroBuildSigningPlugin@4
+ displayName: Install MicroBuild plugin
+ inputs: ${{ parameters.microbuildTaskInputs }}
+ env: ${{ parameters.microbuildEnv }}
+ continueOnError: ${{ parameters.continueOnError }}
+ condition: ${{ parameters.condition }}
diff --git a/eng/common/core-templates/steps/install-microbuild.yml b/eng/common/core-templates/steps/install-microbuild.yml
index 553fce66b94..76a54e157fd 100644
--- a/eng/common/core-templates/steps/install-microbuild.yml
+++ b/eng/common/core-templates/steps/install-microbuild.yml
@@ -4,6 +4,8 @@ parameters:
# Enable install tasks for MicroBuild on Mac and Linux
# Will be ignored if 'enableMicrobuild' is false or 'Agent.Os' is 'Windows_NT'
enableMicrobuildForMacAndLinux: false
+ # Enable preview version of MB signing plugin
+ enablePreviewMicrobuild: false
# Determines whether the ESRP service connection information should be passed to the signing plugin.
# This overlaps with _SignType to some degree. We only need the service connection for real signing.
# It's important that the service connection not be passed to the MicroBuildSigningPlugin task in this place.
@@ -13,6 +15,8 @@ parameters:
microbuildUseESRP: true
# Microbuild installation directory
microBuildOutputFolder: $(Agent.TempDirectory)/MicroBuild
+ # Microbuild version
+ microbuildPluginVersion: 'latest'
continueOnError: false
@@ -69,42 +73,46 @@ steps:
# YAML expansion, and Windows vs. Linux/Mac uses different service connections. However,
# we can avoid including the MB install step if not enabled at all. This avoids a bunch of
# extra pipeline authorizations, since most pipelines do not sign on non-Windows.
- - task: MicroBuildSigningPlugin@4
- displayName: Install MicroBuild plugin (Windows)
- inputs:
- signType: $(_SignType)
- zipSources: false
- feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json
- ${{ if eq(parameters.microbuildUseESRP, true) }}:
- ConnectedServiceName: 'MicroBuild Signing Task (DevDiv)'
- ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}:
- ConnectedPMEServiceName: 6cc74545-d7b9-4050-9dfa-ebefcc8961ea
- ${{ else }}:
- ConnectedPMEServiceName: 248d384a-b39b-46e3-8ad5-c2c210d5e7ca
- env:
- TeamName: $(_TeamName)
- MicroBuildOutputFolderOverride: ${{ parameters.microBuildOutputFolder }}
- SYSTEM_ACCESSTOKEN: $(System.AccessToken)
- continueOnError: ${{ parameters.continueOnError }}
- condition: and(succeeded(), eq(variables['Agent.Os'], 'Windows_NT'), in(variables['_SignType'], 'real', 'test'))
-
- - ${{ if eq(parameters.enableMicrobuildForMacAndLinux, true) }}:
- - task: MicroBuildSigningPlugin@4
- displayName: Install MicroBuild plugin (non-Windows)
- inputs:
+ - template: /eng/common/core-templates/steps/install-microbuild-impl.yml
+ parameters:
+ enablePreviewMicrobuild: ${{ parameters.enablePreviewMicrobuild }}
+ microbuildTaskInputs:
signType: $(_SignType)
zipSources: false
feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json
- workingDirectory: ${{ parameters.microBuildOutputFolder }}
+ version: ${{ parameters.microbuildPluginVersion }}
${{ if eq(parameters.microbuildUseESRP, true) }}:
ConnectedServiceName: 'MicroBuild Signing Task (DevDiv)'
${{ if eq(variables['System.TeamProject'], 'DevDiv') }}:
- ConnectedPMEServiceName: beb8cb23-b303-4c95-ab26-9e44bc958d39
+ ConnectedPMEServiceName: 6cc74545-d7b9-4050-9dfa-ebefcc8961ea
${{ else }}:
- ConnectedPMEServiceName: c24de2a5-cc7a-493d-95e4-8e5ff5cad2bc
- env:
+ ConnectedPMEServiceName: 248d384a-b39b-46e3-8ad5-c2c210d5e7ca
+ microbuildEnv:
TeamName: $(_TeamName)
MicroBuildOutputFolderOverride: ${{ parameters.microBuildOutputFolder }}
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
continueOnError: ${{ parameters.continueOnError }}
- condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT'), eq(variables['_SignType'], 'real'))
+ condition: and(succeeded(), eq(variables['Agent.Os'], 'Windows_NT'), in(variables['_SignType'], 'real', 'test'))
+
+ - ${{ if eq(parameters.enableMicrobuildForMacAndLinux, true) }}:
+ - template: /eng/common/core-templates/steps/install-microbuild-impl.yml
+ parameters:
+ enablePreviewMicrobuild: ${{ parameters.enablePreviewMicrobuild }}
+ microbuildTaskInputs:
+ signType: $(_SignType)
+ zipSources: false
+ feedSource: https://dnceng.pkgs.visualstudio.com/_packaging/MicroBuildToolset/nuget/v3/index.json
+ version: ${{ parameters.microbuildPluginVersion }}
+ workingDirectory: ${{ parameters.microBuildOutputFolder }}
+ ${{ if eq(parameters.microbuildUseESRP, true) }}:
+ ConnectedServiceName: 'MicroBuild Signing Task (DevDiv)'
+ ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}:
+ ConnectedPMEServiceName: beb8cb23-b303-4c95-ab26-9e44bc958d39
+ ${{ else }}:
+ ConnectedPMEServiceName: c24de2a5-cc7a-493d-95e4-8e5ff5cad2bc
+ microbuildEnv:
+ TeamName: $(_TeamName)
+ MicroBuildOutputFolderOverride: ${{ parameters.microBuildOutputFolder }}
+ SYSTEM_ACCESSTOKEN: $(System.AccessToken)
+ continueOnError: ${{ parameters.continueOnError }}
+ condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT'), eq(variables['_SignType'], 'real'))
diff --git a/eng/common/core-templates/steps/publish-logs.yml b/eng/common/core-templates/steps/publish-logs.yml
index 4411b3b0eeb..c496f3d0dc7 100644
--- a/eng/common/core-templates/steps/publish-logs.yml
+++ b/eng/common/core-templates/steps/publish-logs.yml
@@ -32,7 +32,6 @@ steps:
-runtimeSourceFeedKey '$(dotnetbuilds-internal-container-read-token-base64)'
'$(publishing-dnceng-devdiv-code-r-build-re)'
'$(akams-client-id)'
- '$(dn-bot-all-orgs-build-rw-code-rw)'
'$(System.AccessToken)'
${{parameters.CustomSensitiveDataList}}
continueOnError: true
@@ -57,3 +56,4 @@ steps:
condition: always()
retryCountOnTaskFailure: 10 # for any files being locked
isProduction: false # logs are non-production artifacts
+
diff --git a/eng/common/core-templates/steps/send-to-helix.yml b/eng/common/core-templates/steps/send-to-helix.yml
index 68fa739c4ab..ec7a2000399 100644
--- a/eng/common/core-templates/steps/send-to-helix.yml
+++ b/eng/common/core-templates/steps/send-to-helix.yml
@@ -10,6 +10,7 @@ parameters:
HelixConfiguration: '' # optional -- additional property attached to a job
HelixPreCommands: '' # optional -- commands to run before Helix work item execution
HelixPostCommands: '' # optional -- commands to run after Helix work item execution
+ UseHelixMonitor: false # optional -- true will submit Helix jobs configured for the standalone Helix Job Monitor (results are reported/waited on out-of-band; this step will not wait, and WaitForWorkItemCompletion will be overridden)
WorkItemDirectory: '' # optional -- a payload directory to zip up and send to Helix; requires WorkItemCommand; incompatible with XUnitProjects
WorkItemCommand: '' # optional -- a command to execute on the payload; requires WorkItemDirectory; incompatible with XUnitProjects
WorkItemTimeout: '' # optional -- a timeout in TimeSpan.Parse-ready value (e.g. 00:02:00) for the work item command; requires WorkItemDirectory; incompatible with XUnitProjects
@@ -31,7 +32,15 @@ parameters:
continueOnError: false # optional -- determines whether to continue the build if the step errors; defaults to false
steps:
- - powershell: 'powershell "$env:BUILD_SOURCESDIRECTORY\eng\common\msbuild.ps1 $env:BUILD_SOURCESDIRECTORY/${{ parameters.HelixProjectPath }} /restore /p:TreatWarningsAsErrors=false ${{ parameters.HelixProjectArguments }} /t:Test /bl:$env:BUILD_SOURCESDIRECTORY\artifacts\log\$env:BuildConfig\SendToHelix.binlog"'
+ - powershell: >
+ $(Build.SourcesDirectory)\eng\common\msbuild.ps1
+ $(Build.SourcesDirectory)/${{ parameters.HelixProjectPath }}
+ /restore
+ /p:TreatWarningsAsErrors=false
+ /p:EnableHelixJobMonitor=${{ parameters.UseHelixMonitor }}
+ ${{ parameters.HelixProjectArguments }}
+ /t:Test
+ /bl:$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/SendToHelix.binlog
displayName: ${{ parameters.DisplayNamePrefix }} (Windows)
env:
BuildConfig: $(_BuildConfig)
@@ -61,7 +70,15 @@ steps:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
condition: and(${{ parameters.condition }}, eq(variables['Agent.Os'], 'Windows_NT'))
continueOnError: ${{ parameters.continueOnError }}
- - script: $BUILD_SOURCESDIRECTORY/eng/common/msbuild.sh $BUILD_SOURCESDIRECTORY/${{ parameters.HelixProjectPath }} /restore /p:TreatWarningsAsErrors=false ${{ parameters.HelixProjectArguments }} /t:Test /bl:$BUILD_SOURCESDIRECTORY/artifacts/log/$BuildConfig/SendToHelix.binlog
+ - script: >
+ $(Build.SourcesDirectory)/eng/common/msbuild.sh
+ $(Build.SourcesDirectory)/${{ parameters.HelixProjectPath }}
+ /restore
+ /p:TreatWarningsAsErrors=false
+ /p:EnableHelixJobMonitor=${{ parameters.UseHelixMonitor }}
+ ${{ parameters.HelixProjectArguments }}
+ /t:Test
+ /bl:$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/SendToHelix.binlog
displayName: ${{ parameters.DisplayNamePrefix }} (Unix)
env:
BuildConfig: $(_BuildConfig)
@@ -91,3 +108,4 @@ steps:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
condition: and(${{ parameters.condition }}, ne(variables['Agent.Os'], 'Windows_NT'))
continueOnError: ${{ parameters.continueOnError }}
+
diff --git a/eng/common/core-templates/steps/source-build.yml b/eng/common/core-templates/steps/source-build.yml
index 09ae5cd73ae..b75f59c428d 100644
--- a/eng/common/core-templates/steps/source-build.yml
+++ b/eng/common/core-templates/steps/source-build.yml
@@ -24,7 +24,7 @@ steps:
# in the default public locations.
internalRuntimeDownloadArgs=
if [ '$(dotnetbuilds-internal-container-read-token-base64)' != '$''(dotnetbuilds-internal-container-read-token-base64)' ]; then
- internalRuntimeDownloadArgs='/p:DotNetRuntimeSourceFeed=https://ci.dot.net/internal /p:DotNetRuntimeSourceFeedKey=$(dotnetbuilds-internal-container-read-token-base64) --runtimesourcefeed https://ci.dot.net/internal --runtimesourcefeedkey '$(dotnetbuilds-internal-container-read-token-base64)''
+ internalRuntimeDownloadArgs='/p:DotNetRuntimeSourceFeed=https://ci.dot.net/internal /p:DotNetRuntimeSourceFeedKey=$(dotnetbuilds-internal-container-read-token-base64) --runtimesourcefeed https://ci.dot.net/internal --runtimesourcefeedkey $(dotnetbuilds-internal-container-read-token-base64)'
fi
buildConfig=Release
diff --git a/eng/common/core-templates/steps/source-index-stage1-publish.yml b/eng/common/core-templates/steps/source-index-stage1-publish.yml
index 6e7666b4dcf..fdca622357f 100644
--- a/eng/common/core-templates/steps/source-index-stage1-publish.yml
+++ b/eng/common/core-templates/steps/source-index-stage1-publish.yml
@@ -1,21 +1,21 @@
parameters:
- sourceIndexUploadPackageVersion: 2.0.0-20250818.1
- sourceIndexProcessBinlogPackageVersion: 1.0.1-20250818.1
+ sourceIndexUploadPackageVersion: 2.0.0-20260521.2
+ sourceIndexProcessBinlogPackageVersion: 1.0.1-20260521.2
sourceIndexPackageSource: https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json
binlogPath: artifacts/log/Debug/Build.binlog
steps:
- task: UseDotNet@2
- displayName: "Source Index: Use .NET 9 SDK"
+ displayName: "Source Index: Use .NET 10 SDK"
inputs:
packageType: sdk
- version: 9.0.x
+ version: 10.0.x
installationPath: $(Agent.TempDirectory)/dotnet
workingDirectory: $(Agent.TempDirectory)
- script: |
- $(Agent.TempDirectory)/dotnet/dotnet tool install BinLogToSln --version ${{parameters.sourceIndexProcessBinlogPackageVersion}} --source ${{parameters.SourceIndexPackageSource}} --tool-path $(Agent.TempDirectory)/.source-index/tools
- $(Agent.TempDirectory)/dotnet/dotnet tool install UploadIndexStage1 --version ${{parameters.sourceIndexUploadPackageVersion}} --source ${{parameters.SourceIndexPackageSource}} --tool-path $(Agent.TempDirectory)/.source-index/tools
+ $(Agent.TempDirectory)/dotnet/dotnet tool install BinLogToSln --version ${{parameters.sourceIndexProcessBinlogPackageVersion}} --source ${{parameters.sourceIndexPackageSource}} --tool-path $(Agent.TempDirectory)/.source-index/tools
+ $(Agent.TempDirectory)/dotnet/dotnet tool install UploadIndexStage1 --version ${{parameters.sourceIndexUploadPackageVersion}} --source ${{parameters.sourceIndexPackageSource}} --tool-path $(Agent.TempDirectory)/.source-index/tools
displayName: "Source Index: Download netsourceindex Tools"
# Set working directory to temp directory so 'dotnet' doesn't try to use global.json and use the repo's sdk.
workingDirectory: $(Agent.TempDirectory)
diff --git a/eng/common/cross/build-rootfs.sh b/eng/common/cross/build-rootfs.sh
index 3150ccac6fc..38a3512f148 100755
--- a/eng/common/cross/build-rootfs.sh
+++ b/eng/common/cross/build-rootfs.sh
@@ -18,7 +18,10 @@ usage()
echo "--skipsigcheck - optional, will skip package signature checks (allowing untrusted packages)."
echo "--skipemulation - optional, will skip qemu and debootstrap requirement when building environment for debian based systems."
echo "--use-mirror - optional, use mirror URL to fetch resources, when available."
- echo "--jobs N - optional, restrict to N jobs."
+ echo "--ubuntu-repo - optional, override the Ubuntu apt repository base URL."
+ echo "--debian-repo - optional, override the Debian apt repository base URL."
+ echo "--alpine-repo - optional, override the Alpine Linux repository base URL."
+ echo "--jobs N (or --use-jobs N) - optional, restrict to N jobs."
exit 1
}
@@ -144,6 +147,9 @@ __KeyringFile="/usr/share/keyrings/ubuntu-archive-keyring.gpg"
__SkipSigCheck=0
__SkipEmulation=0
__UseMirror=0
+__UbuntuRepoOverride=
+__DebianRepoOverride=
+__AlpineRepoOverride=
__UnprocessedBuildArgs=
while :; do
@@ -397,6 +403,31 @@ while :; do
--use-mirror)
__UseMirror=1
;;
+ --ubuntu-repo|-ubuntu-repo)
+ shift
+ if [[ "$#" -le 0 ]]; then
+ echo "ERROR: --ubuntu-repo requires a URL argument."
+ usage
+ fi
+ __UbuntuRepoOverride="$1"
+ ;;
+ --debian-repo|-debian-repo)
+ shift
+ if [[ "$#" -le 0 ]]; then
+ echo "ERROR: --debian-repo requires a URL argument."
+ usage
+ fi
+ __DebianRepoOverride="$1"
+ ;;
+ --alpine-repo|-alpine-repo)
+ shift
+ if [[ "$#" -le 0 ]]; then
+ echo "ERROR: --alpine-repo requires a URL argument."
+ usage
+ fi
+ __AlpineRepoOverride="$1"
+ ;;
+ # Removed duplicate/invalid option handling block (was breaking case statement parsing).
--use-jobs)
shift
MAXJOBS=$1
@@ -422,9 +453,12 @@ case "$__AlpineVersion" in
elif [[ "$__AlpineArch" == "x86" ]]; then
__AlpineVersion=3.17 # minimum version that supports lldb-dev
__AlpinePackages+=" llvm15-libs"
- elif [[ "$__AlpineArch" == "riscv64" || "$__AlpineArch" == "loongarch64" ]]; then
+ elif [[ "$__AlpineArch" == "loongarch64" ]]; then
__AlpineVersion=3.21 # minimum version that supports lldb-dev
__AlpinePackages+=" llvm19-libs"
+ elif [[ "$__AlpineArch" == "riscv64" ]]; then
+ __AlpineVersion=3.22 # lldb-dev requires 3.21+, but 3.22+ provides the newer linux-headers needed for RISC-V extension probes
+ __AlpinePackages+=" llvm20-libs"
elif [[ -n "$__AlpineMajorVersion" ]]; then
# use whichever alpine version is provided and select the latest toolchain libs
__AlpineLlvmLibsLookup=1
@@ -446,6 +480,12 @@ if [[ -z "$__UbuntuRepo" ]]; then
__UbuntuRepo="https://ports.ubuntu.com/"
fi
+if [[ -n "$__UbuntuRepoOverride" && "$__KeyringFile" == *ubuntu* ]]; then
+ __UbuntuRepo="$__UbuntuRepoOverride"
+elif [[ -n "$__DebianRepoOverride" && "$__KeyringFile" == *debian* ]]; then
+ __UbuntuRepo="$__DebianRepoOverride"
+fi
+
if [[ -n "$__LLVM_MajorVersion" ]]; then
__UbuntuPackages+=" libclang-common-${__LLVM_MajorVersion}${__LLVM_MinorVersion:+.$__LLVM_MinorVersion}-dev"
fi
@@ -486,6 +526,7 @@ if [[ "$__CodeName" == "alpine" ]]; then
__ApkToolsDir="$(mktemp -d)"
__ApkKeysDir="$(mktemp -d)"
arch="$(uname -m)"
+ __AlpineRepo="${__AlpineRepoOverride:-https://dl-cdn.alpinelinux.org/alpine}"
ensureDownloadTool
@@ -530,15 +571,15 @@ if [[ "$__CodeName" == "alpine" ]]; then
# initialize DB
# shellcheck disable=SC2086
"$__ApkToolsDir/apk.static" \
- -X "https://dl-cdn.alpinelinux.org/alpine/$version/main" \
- -X "https://dl-cdn.alpinelinux.org/alpine/$version/community" \
+ -X "$__AlpineRepo/$version/main" \
+ -X "$__AlpineRepo/$version/community" \
-U $__ApkSignatureArg --root "$__RootfsDir" --arch "$__AlpineArch" --initdb add
if [[ "$__AlpineLlvmLibsLookup" == 1 ]]; then
# shellcheck disable=SC2086
__AlpinePackages+=" $("$__ApkToolsDir/apk.static" \
- -X "https://dl-cdn.alpinelinux.org/alpine/$version/main" \
- -X "https://dl-cdn.alpinelinux.org/alpine/$version/community" \
+ -X "$__AlpineRepo/$version/main" \
+ -X "$__AlpineRepo/$version/community" \
-U $__ApkSignatureArg --root "$__RootfsDir" --arch "$__AlpineArch" \
search 'llvm*-libs' | grep -E '^llvm' | sort | tail -1 | sed 's/-[^-]*//2g')"
fi
@@ -546,8 +587,8 @@ if [[ "$__CodeName" == "alpine" ]]; then
# install all packages in one go
# shellcheck disable=SC2086
"$__ApkToolsDir/apk.static" \
- -X "https://dl-cdn.alpinelinux.org/alpine/$version/main" \
- -X "https://dl-cdn.alpinelinux.org/alpine/$version/community" \
+ -X "$__AlpineRepo/$version/main" \
+ -X "$__AlpineRepo/$version/community" \
-U $__ApkSignatureArg --root "$__RootfsDir" --arch "$__AlpineArch" $__NoEmulationArg \
add $__AlpinePackages
diff --git a/eng/common/cross/toolchain.cmake b/eng/common/cross/toolchain.cmake
index f65c689f695..70b71395e3b 100644
--- a/eng/common/cross/toolchain.cmake
+++ b/eng/common/cross/toolchain.cmake
@@ -87,6 +87,8 @@ elseif(TARGET_ARCH_NAME STREQUAL "ppc64le")
set(CMAKE_SYSTEM_PROCESSOR ppc64le)
if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/powerpc64le-alpine-linux-musl)
set(TOOLCHAIN "powerpc64le-alpine-linux-musl")
+ elseif(FREEBSD)
+ set(TOOLCHAIN "powerpc64le-unknown-freebsd14")
else()
set(TOOLCHAIN "powerpc64le-linux-gnu")
endif()
@@ -159,6 +161,7 @@ if(TIZEN)
else()
find_toolchain_dir("${CROSS_ROOTFS}/usr/lib64/gcc/${TIZEN_TOOLCHAIN}")
endif()
+
include_directories(SYSTEM ${TIZEN_TOOLCHAIN_PATH}/include/c++)
include_directories(SYSTEM ${TIZEN_TOOLCHAIN_PATH}/include/c++/${TIZEN_TOOLCHAIN})
endif()
@@ -226,7 +229,7 @@ elseif(HAIKU)
set(CMAKE_C_STANDARD_LIBRARIES "${CMAKE_C_STANDARD_LIBRARIES} -lssp")
set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lssp")
- if ("$ENV{CCC_CC}" MATCHES ".*gcc.*")
+ if ($ENV{CCC_CC} MATCHES ".*gcc.*")
set(CMAKE_PROGRAM_PATH "${CMAKE_PROGRAM_PATH};${CROSS_ROOTFS}/cross-tools-x86_64/bin")
locate_toolchain_exec(gcc CMAKE_C_COMPILER)
locate_toolchain_exec(g++ CMAKE_CXX_COMPILER)
diff --git a/eng/common/darc-init.sh b/eng/common/darc-init.sh
index e6ba4ee28c1..b56d40e5706 100755
--- a/eng/common/darc-init.sh
+++ b/eng/common/darc-init.sh
@@ -5,7 +5,7 @@ darcVersion=''
versionEndpoint='https://maestro.dot.net/api/assets/darc-version?api-version=2020-02-20'
verbosity='minimal'
-while [[ $# > 0 ]]; do
+while [[ $# -gt 0 ]]; do
opt="$(echo "$1" | tr "[:upper:]" "[:lower:]")"
case "$opt" in
--darcversion)
diff --git a/eng/common/dotnet-install.ps1 b/eng/common/dotnet-install.ps1
index 811f0f717f7..b6d45f2bdc4 100644
--- a/eng/common/dotnet-install.ps1
+++ b/eng/common/dotnet-install.ps1
@@ -4,13 +4,20 @@ Param(
[string] $architecture = '',
[string] $version = 'Latest',
[string] $runtime = 'dotnet',
+ [string] $dotnetPath = '',
[string] $RuntimeSourceFeed = '',
[string] $RuntimeSourceFeedKey = ''
)
. $PSScriptRoot\tools.ps1
-$dotnetRoot = Join-Path $RepoRoot '.dotnet'
+if (-not [string]::IsNullOrEmpty($dotnetPath)) {
+ $dotnetRoot = $dotnetPath
+} elseif (-not [string]::IsNullOrEmpty($env:DOTNET_GLOBAL_INSTALL_DIR)) {
+ $dotnetRoot = $env:DOTNET_GLOBAL_INSTALL_DIR
+} else {
+ $dotnetRoot = Join-Path $RepoRoot '.dotnet'
+}
$installdir = $dotnetRoot
try {
diff --git a/eng/common/dotnet-install.sh b/eng/common/dotnet-install.sh
index 7b9d97e3bd4..58a7e6f384e 100755
--- a/eng/common/dotnet-install.sh
+++ b/eng/common/dotnet-install.sh
@@ -16,9 +16,10 @@ scriptroot="$( cd -P "$( dirname "$source" )" && pwd )"
version='Latest'
architecture=''
runtime='dotnet'
+dotnetPath=''
runtimeSourceFeed=''
runtimeSourceFeedKey=''
-while [[ $# > 0 ]]; do
+while [[ $# -gt 0 ]]; do
opt="$(echo "$1" | tr "[:upper:]" "[:lower:]")"
case "$opt" in
-version|-v)
@@ -33,6 +34,10 @@ while [[ $# > 0 ]]; do
shift
runtime="$1"
;;
+ -dotnetpath)
+ shift
+ dotnetPath="$1"
+ ;;
-runtimesourcefeed)
shift
runtimeSourceFeed="$1"
@@ -80,7 +85,13 @@ case $cpuname in
;;
esac
-dotnetRoot="${repo_root}.dotnet"
+if [[ -n "${dotnetPath:-}" ]]; then
+ dotnetRoot="$dotnetPath"
+elif [[ -n "${DOTNET_GLOBAL_INSTALL_DIR:-}" ]]; then
+ dotnetRoot="$DOTNET_GLOBAL_INSTALL_DIR"
+else
+ dotnetRoot="${repo_root}.dotnet"
+fi
if [[ $architecture != "" ]] && [[ $architecture != $buildarch ]]; then
dotnetRoot="$dotnetRoot/$architecture"
fi
diff --git a/eng/common/dotnet.sh b/eng/common/dotnet.sh
index 2ef68235675..f6d24871c1d 100755
--- a/eng/common/dotnet.sh
+++ b/eng/common/dotnet.sh
@@ -19,7 +19,7 @@ source $scriptroot/tools.sh
InitializeDotNetCli true # install
# Invoke acquired SDK with args if they are provided
-if [[ $# > 0 ]]; then
+if [[ $# -gt 0 ]]; then
__dotnetDir=${_InitializeDotNetCli}
dotnetPath=${__dotnetDir}/dotnet
${dotnetPath} "$@"
diff --git a/eng/common/internal-feed-operations.sh b/eng/common/internal-feed-operations.sh
index 9378223ba09..6299e7effd4 100755
--- a/eng/common/internal-feed-operations.sh
+++ b/eng/common/internal-feed-operations.sh
@@ -100,7 +100,7 @@ operation=''
authToken=''
repoName=''
-while [[ $# > 0 ]]; do
+while [[ $# -gt 0 ]]; do
opt="$(echo "$1" | tr "[:upper:]" "[:lower:]")"
case "$opt" in
--operation)
diff --git a/eng/common/msbuild.ps1 b/eng/common/msbuild.ps1
index f041e5ddd95..495d533a909 100644
--- a/eng/common/msbuild.ps1
+++ b/eng/common/msbuild.ps1
@@ -14,7 +14,11 @@ Param(
try {
if ($ci) {
- $nodeReuse = $false
+ # Disable node reuse on CI unless explicitly opted in via MSBUILD_NODEREUSE_ENABLED.
+ # Internal testing only; this env var will be replaced with a switch (https://github.com/dotnet/arcade/issues/17013) and must not be depended on.
+ if ($env:MSBUILD_NODEREUSE_ENABLED -ne "1") {
+ $nodeReuse = $false
+ }
}
MSBuild @extraArgs
diff --git a/eng/common/msbuild.sh b/eng/common/msbuild.sh
index 20d3dad5435..333be3232fc 100755
--- a/eng/common/msbuild.sh
+++ b/eng/common/msbuild.sh
@@ -51,7 +51,11 @@ done
. "$scriptroot/tools.sh"
if [[ "$ci" == true ]]; then
- node_reuse=false
+ # Disable node reuse on CI unless explicitly opted in via MSBUILD_NODEREUSE_ENABLED.
+ # Internal testing only; this env var will be replaced with a switch (https://github.com/dotnet/arcade/issues/17013) and must not be depended on.
+ if [[ "${MSBUILD_NODEREUSE_ENABLED:-}" != "1" ]]; then
+ node_reuse=false
+ fi
fi
MSBuild $extra_args
diff --git a/eng/common/native/NativeAotSupported.props b/eng/common/native/NativeAotSupported.props
index 559a6663929..cdff9ef0361 100644
--- a/eng/common/native/NativeAotSupported.props
+++ b/eng/common/native/NativeAotSupported.props
@@ -13,6 +13,8 @@
<_NativeAotSupportedArch Condition="
'$(TargetArchitecture)' != 'wasm' and
+ '$(TargetArchitecture)' != 's390x' and
+ '$(TargetArchitecture)' != 'ppc64le' and
('$(TargetArchitecture)' != 'x86' or '$(TargetOS)' == 'windows')
">true
diff --git a/eng/common/native/init-os-and-arch.sh b/eng/common/native/init-os-and-arch.sh
index 38921d4338f..62d62fed522 100644
--- a/eng/common/native/init-os-and-arch.sh
+++ b/eng/common/native/init-os-and-arch.sh
@@ -27,6 +27,10 @@ if [ "$os" = "sunos" ]; then
os="solaris"
fi
CPUName=$(isainfo -n)
+elif [ "$os" = "freebsd" ]; then
+ # FreeBSD's `uname -m` is the machine class ("powerpc" for every PowerPC
+ # variant); `uname -p` gives the specific processor (e.g. powerpc64le).
+ CPUName=$(uname -p)
else
# For the rest of the operating systems, use uname(1) to determine what the CPU is.
CPUName=$(uname -m)
@@ -75,7 +79,7 @@ case "$CPUName" in
arch=s390x
;;
- ppc64le)
+ ppc64le|powerpc64le)
arch=ppc64le
;;
*)
diff --git a/eng/common/pipeline-logging-functions.ps1 b/eng/common/pipeline-logging-functions.ps1
index 8e422c561e4..9f85c291708 100644
--- a/eng/common/pipeline-logging-functions.ps1
+++ b/eng/common/pipeline-logging-functions.ps1
@@ -32,7 +32,7 @@ function Write-PipelineTelemetryError {
$PSBoundParameters.Remove('Category') | Out-Null
if ($Force -Or ((Test-Path variable:ci) -And $ci)) {
- $Message = "(NETCORE_ENGINEERING_TELEMETRY=$Category) $Message"
+ $Message = "($Category) $Message"
}
$PSBoundParameters.Remove('Message') | Out-Null
$PSBoundParameters.Add('Message', $Message)
diff --git a/eng/common/post-build/redact-logs.ps1 b/eng/common/post-build/redact-logs.ps1
index c1e4104b79a..672f4e2652e 100644
--- a/eng/common/post-build/redact-logs.ps1
+++ b/eng/common/post-build/redact-logs.ps1
@@ -9,7 +9,8 @@ param(
[Parameter(Mandatory=$false)][string] $TokensFilePath,
[Parameter(ValueFromRemainingArguments=$true)][String[]]$TokensToRedact,
[Parameter(Mandatory=$false)][string] $runtimeSourceFeed,
- [Parameter(Mandatory=$false)][string] $runtimeSourceFeedKey)
+ [Parameter(Mandatory=$false)][string] $runtimeSourceFeedKey
+)
try {
$ErrorActionPreference = 'Stop'
diff --git a/eng/common/post-build/sourcelink-validation.ps1 b/eng/common/post-build/sourcelink-validation.ps1
deleted file mode 100644
index 1976ef70fb8..00000000000
--- a/eng/common/post-build/sourcelink-validation.ps1
+++ /dev/null
@@ -1,327 +0,0 @@
-param(
- [Parameter(Mandatory=$true)][string] $InputPath, # Full path to directory where Symbols.NuGet packages to be checked are stored
- [Parameter(Mandatory=$true)][string] $ExtractPath, # Full path to directory where the packages will be extracted during validation
- [Parameter(Mandatory=$false)][string] $GHRepoName, # GitHub name of the repo including the Org. E.g., dotnet/arcade
- [Parameter(Mandatory=$false)][string] $GHCommit, # GitHub commit SHA used to build the packages
- [Parameter(Mandatory=$true)][string] $SourcelinkCliVersion # Version of SourceLink CLI to use
-)
-
-$ErrorActionPreference = 'Stop'
-Set-StrictMode -Version 2.0
-
-# `tools.ps1` checks $ci to perform some actions. Since the post-build
-# scripts don't necessarily execute in the same agent that run the
-# build.ps1/sh script this variable isn't automatically set.
-$ci = $true
-$disableConfigureToolsetImport = $true
-. $PSScriptRoot\..\tools.ps1
-
-# Cache/HashMap (File -> Exist flag) used to consult whether a file exist
-# in the repository at a specific commit point. This is populated by inserting
-# all files present in the repo at a specific commit point.
-$global:RepoFiles = @{}
-
-# Maximum number of jobs to run in parallel
-$MaxParallelJobs = 16
-
-$MaxRetries = 5
-$RetryWaitTimeInSeconds = 30
-
-# Wait time between check for system load
-$SecondsBetweenLoadChecks = 10
-
-if (!$InputPath -or !(Test-Path $InputPath)){
- Write-Host "No files to validate."
- ExitWithExitCode 0
-}
-
-$ValidatePackage = {
- param(
- [string] $PackagePath # Full path to a Symbols.NuGet package
- )
-
- . $using:PSScriptRoot\..\tools.ps1
-
- # Ensure input file exist
- if (!(Test-Path $PackagePath)) {
- Write-Host "Input file does not exist: $PackagePath"
- return [pscustomobject]@{
- result = 1
- packagePath = $PackagePath
- }
- }
-
- # Extensions for which we'll look for SourceLink information
- # For now we'll only care about Portable & Embedded PDBs
- $RelevantExtensions = @('.dll', '.exe', '.pdb')
-
- Write-Host -NoNewLine 'Validating ' ([System.IO.Path]::GetFileName($PackagePath)) '...'
-
- $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath)
- $ExtractPath = Join-Path -Path $using:ExtractPath -ChildPath $PackageId
- $FailedFiles = 0
-
- Add-Type -AssemblyName System.IO.Compression.FileSystem
-
- [System.IO.Directory]::CreateDirectory($ExtractPath) | Out-Null
-
- try {
- $zip = [System.IO.Compression.ZipFile]::OpenRead($PackagePath)
-
- $zip.Entries |
- Where-Object {$RelevantExtensions -contains [System.IO.Path]::GetExtension($_.Name)} |
- ForEach-Object {
- $FileName = $_.FullName
- $Extension = [System.IO.Path]::GetExtension($_.Name)
- $FakeName = -Join((New-Guid), $Extension)
- $TargetFile = Join-Path -Path $ExtractPath -ChildPath $FakeName
-
- # We ignore resource DLLs
- if ($FileName.EndsWith('.resources.dll')) {
- return [pscustomobject]@{
- result = 0
- packagePath = $PackagePath
- }
- }
-
- [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, $TargetFile, $true)
-
- $ValidateFile = {
- param(
- [string] $FullPath, # Full path to the module that has to be checked
- [string] $RealPath,
- [ref] $FailedFiles
- )
-
- $sourcelinkExe = "$env:USERPROFILE\.dotnet\tools"
- $sourcelinkExe = Resolve-Path "$sourcelinkExe\sourcelink.exe"
- $SourceLinkInfos = & $sourcelinkExe print-urls $FullPath | Out-String
-
- if ($LASTEXITCODE -eq 0 -and -not ([string]::IsNullOrEmpty($SourceLinkInfos))) {
- $NumFailedLinks = 0
-
- # We only care about Http addresses
- $Matches = (Select-String '(http[s]?)(:\/\/)([^\s,]+)' -Input $SourceLinkInfos -AllMatches).Matches
-
- if ($Matches.Count -ne 0) {
- $Matches.Value |
- ForEach-Object {
- $Link = $_
- $CommitUrl = "https://raw.githubusercontent.com/${using:GHRepoName}/${using:GHCommit}/"
-
- $FilePath = $Link.Replace($CommitUrl, "")
- $Status = 200
- $Cache = $using:RepoFiles
-
- $attempts = 0
-
- while ($attempts -lt $using:MaxRetries) {
- if ( !($Cache.ContainsKey($FilePath)) ) {
- try {
- $Uri = $Link -as [System.URI]
-
- if ($Link -match "submodules") {
- # Skip submodule links until sourcelink properly handles submodules
- $Status = 200
- }
- elseif ($Uri.AbsoluteURI -ne $null -and ($Uri.Host -match 'github' -or $Uri.Host -match 'githubusercontent')) {
- # Only GitHub links are valid
- $Status = (Invoke-WebRequest -Uri $Link -UseBasicParsing -Method HEAD -TimeoutSec 5).StatusCode
- }
- else {
- # If it's not a github link, we want to break out of the loop and not retry.
- $Status = 0
- $attempts = $using:MaxRetries
- }
- }
- catch {
- Write-Host $_
- $Status = 0
- }
- }
-
- if ($Status -ne 200) {
- $attempts++
-
- if ($attempts -lt $using:MaxRetries)
- {
- $attemptsLeft = $using:MaxRetries - $attempts
- Write-Warning "Download failed, $attemptsLeft attempts remaining, will retry in $using:RetryWaitTimeInSeconds seconds"
- Start-Sleep -Seconds $using:RetryWaitTimeInSeconds
- }
- else {
- if ($NumFailedLinks -eq 0) {
- if ($FailedFiles.Value -eq 0) {
- Write-Host
- }
-
- Write-Host "`tFile $RealPath has broken links:"
- }
-
- Write-Host "`t`tFailed to retrieve $Link"
-
- $NumFailedLinks++
- }
- }
- else {
- break
- }
- }
- }
- }
-
- if ($NumFailedLinks -ne 0) {
- $FailedFiles.value++
- $global:LASTEXITCODE = 1
- }
- }
- }
-
- &$ValidateFile $TargetFile $FileName ([ref]$FailedFiles)
- }
- }
- catch {
- Write-Host $_
- }
- finally {
- $zip.Dispose()
- }
-
- if ($FailedFiles -eq 0) {
- Write-Host 'Passed.'
- return [pscustomobject]@{
- result = 0
- packagePath = $PackagePath
- }
- }
- else {
- Write-PipelineTelemetryError -Category 'SourceLink' -Message "$PackagePath has broken SourceLink links."
- return [pscustomobject]@{
- result = 1
- packagePath = $PackagePath
- }
- }
-}
-
-function CheckJobResult(
- $result,
- $packagePath,
- [ref]$ValidationFailures,
- [switch]$logErrors) {
- if ($result -ne '0') {
- if ($logErrors) {
- Write-PipelineTelemetryError -Category 'SourceLink' -Message "$packagePath has broken SourceLink links."
- }
- $ValidationFailures.Value++
- }
-}
-
-function ValidateSourceLinkLinks {
- if ($GHRepoName -ne '' -and !($GHRepoName -Match '^[^\s\/]+/[^\s\/]+$')) {
- if (!($GHRepoName -Match '^[^\s-]+-[^\s]+$')) {
- Write-PipelineTelemetryError -Category 'SourceLink' -Message "GHRepoName should be in the format / or -. '$GHRepoName'"
- ExitWithExitCode 1
- }
- else {
- $GHRepoName = $GHRepoName -replace '^([^\s-]+)-([^\s]+)$', '$1/$2';
- }
- }
-
- if ($GHCommit -ne '' -and !($GHCommit -Match '^[0-9a-fA-F]{40}$')) {
- Write-PipelineTelemetryError -Category 'SourceLink' -Message "GHCommit should be a 40 chars hexadecimal string. '$GHCommit'"
- ExitWithExitCode 1
- }
-
- if ($GHRepoName -ne '' -and $GHCommit -ne '') {
- $RepoTreeURL = -Join('https://api.github.com/repos/', $GHRepoName, '/git/trees/', $GHCommit, '?recursive=1')
- $CodeExtensions = @('.cs', '.vb', '.fs', '.fsi', '.fsx', '.fsscript')
-
- try {
- # Retrieve the list of files in the repo at that particular commit point and store them in the RepoFiles hash
- $Data = Invoke-WebRequest $RepoTreeURL -UseBasicParsing | ConvertFrom-Json | Select-Object -ExpandProperty tree
-
- foreach ($file in $Data) {
- $Extension = [System.IO.Path]::GetExtension($file.path)
-
- if ($CodeExtensions.Contains($Extension)) {
- $RepoFiles[$file.path] = 1
- }
- }
- }
- catch {
- Write-Host "Problems downloading the list of files from the repo. Url used: $RepoTreeURL . Execution will proceed without caching."
- }
- }
- elseif ($GHRepoName -ne '' -or $GHCommit -ne '') {
- Write-Host 'For using the http caching mechanism both GHRepoName and GHCommit should be informed.'
- }
-
- if (Test-Path $ExtractPath) {
- Remove-Item $ExtractPath -Force -Recurse -ErrorAction SilentlyContinue
- }
-
- $ValidationFailures = 0
-
- # Process each NuGet package in parallel
- Get-ChildItem "$InputPath\*.symbols.nupkg" |
- ForEach-Object {
- Write-Host "Starting $($_.FullName)"
- Start-Job -ScriptBlock $ValidatePackage -ArgumentList $_.FullName | Out-Null
- $NumJobs = @(Get-Job -State 'Running').Count
-
- while ($NumJobs -ge $MaxParallelJobs) {
- Write-Host "There are $NumJobs validation jobs running right now. Waiting $SecondsBetweenLoadChecks seconds to check again."
- sleep $SecondsBetweenLoadChecks
- $NumJobs = @(Get-Job -State 'Running').Count
- }
-
- foreach ($Job in @(Get-Job -State 'Completed')) {
- $jobResult = Wait-Job -Id $Job.Id | Receive-Job
- CheckJobResult $jobResult.result $jobResult.packagePath ([ref]$ValidationFailures) -LogErrors
- Remove-Job -Id $Job.Id
- }
- }
-
- foreach ($Job in @(Get-Job)) {
- $jobResult = Wait-Job -Id $Job.Id | Receive-Job
- CheckJobResult $jobResult.result $jobResult.packagePath ([ref]$ValidationFailures)
- Remove-Job -Id $Job.Id
- }
- if ($ValidationFailures -gt 0) {
- Write-PipelineTelemetryError -Category 'SourceLink' -Message "$ValidationFailures package(s) failed validation."
- ExitWithExitCode 1
- }
-}
-
-function InstallSourcelinkCli {
- $sourcelinkCliPackageName = 'sourcelink'
-
- $dotnetRoot = InitializeDotNetCli -install:$true
- $dotnet = "$dotnetRoot\dotnet.exe"
- $toolList = & "$dotnet" tool list --global
-
- if (($toolList -like "*$sourcelinkCliPackageName*") -and ($toolList -like "*$sourcelinkCliVersion*")) {
- Write-Host "SourceLink CLI version $sourcelinkCliVersion is already installed."
- }
- else {
- Write-Host "Installing SourceLink CLI version $sourcelinkCliVersion..."
- Write-Host 'You may need to restart your command window if this is the first dotnet tool you have installed.'
- & "$dotnet" tool install $sourcelinkCliPackageName --version $sourcelinkCliVersion --verbosity "minimal" --global
- }
-}
-
-try {
- InstallSourcelinkCli
-
- foreach ($Job in @(Get-Job)) {
- Remove-Job -Id $Job.Id
- }
-
- ValidateSourceLinkLinks
-}
-catch {
- Write-Host $_.Exception
- Write-Host $_.ScriptStackTrace
- Write-PipelineTelemetryError -Category 'SourceLink' -Message $_
- ExitWithExitCode 1
-}
diff --git a/eng/common/renovate.env b/eng/common/renovate.env
new file mode 100644
index 00000000000..17ecc05d9b1
--- /dev/null
+++ b/eng/common/renovate.env
@@ -0,0 +1,42 @@
+# Renovate Global Configuration
+# https://docs.renovatebot.com/self-hosted-configuration/
+#
+# NOTE: This file uses bash/shell format and is sourced via `. renovate.env`.
+# Values containing spaces or special characters must be quoted.
+
+# Author to use for git commits made by Renovate
+# https://docs.renovatebot.com/configuration-options/#gitauthor
+export RENOVATE_GIT_AUTHOR='.NET Renovate '
+
+# Disable rate limiting for PR creation (0 = unlimited)
+# https://docs.renovatebot.com/presets-default/#prhourlylimitnone
+# https://docs.renovatebot.com/presets-default/#prconcurrentlimitnone
+export RENOVATE_PR_HOURLY_LIMIT=0
+export RENOVATE_PR_CONCURRENT_LIMIT=0
+
+# Skip the onboarding PR that Renovate normally creates for new repos
+# https://docs.renovatebot.com/config-overview/#onboarding
+export RENOVATE_ONBOARDING=false
+
+# Any Renovate config file in the cloned repository is ignored. Only
+# the Renovate config file from the repo where the pipeline is running
+# is used (yes, those are the same repo but the sources may be different).
+# https://docs.renovatebot.com/self-hosted-configuration/#requireconfig
+export RENOVATE_REQUIRE_CONFIG=ignored
+
+# Customize the PR body content. This removes some of the default
+# sections that aren't relevant in a self-hosted config.
+# https://docs.renovatebot.com/configuration-options/#prheader
+# https://docs.renovatebot.com/configuration-options/#prbodynotes
+# https://docs.renovatebot.com/configuration-options/#prbodytemplate
+export RENOVATE_PR_HEADER='## Automated Dependency Update'
+export RENOVATE_PR_BODY_NOTES='["This PR has been created automatically by the [.NET Renovate Bot](https://github.com/dotnet/arcade/blob/main/Documentation/Renovate.md) to update one or more dependencies in your repo. Please review the changes and merge the PR if everything looks good."]'
+export RENOVATE_PR_BODY_TEMPLATE='{{{header}}}{{{table}}}{{{warnings}}}{{{notes}}}{{{changelogs}}}'
+
+# Extend the global config with additional presets
+# https://docs.renovatebot.com/self-hosted-configuration/#globalextends
+# Disable the Dependency Dashboard issue that tracks all updates
+export RENOVATE_GLOBAL_EXTENDS='[":disableDependencyDashboard"]'
+
+# Allow all commands for post-upgrade commands.
+export RENOVATE_ALLOWED_COMMANDS='[".*"]'
diff --git a/eng/common/sdk-task.ps1 b/eng/common/sdk-task.ps1
index b64b66a6275..8d72d803dd2 100644
--- a/eng/common/sdk-task.ps1
+++ b/eng/common/sdk-task.ps1
@@ -4,7 +4,9 @@ Param(
[string] $task,
[string] $verbosity = 'minimal',
[string] $msbuildEngine = $null,
- [switch] $restore,
+ # Restore defaults to on; -restore is retained only so existing consumers that pass it don't break. Use -norestore to opt out.
+ [switch] $restore = $true,
+ [switch] $norestore,
[switch] $prepareMachine,
[switch][Alias('nobl')]$excludeCIBinaryLog,
[switch]$noWarnAsError,
@@ -18,12 +20,23 @@ $ci = $true
$binaryLog = if ($excludeCIBinaryLog) { $false } else { $true }
$warnAsError = if ($noWarnAsError) { $false } else { $true }
+# Reconcile the restore state before importing tools.ps1: it reads $restore at import time to
+# decide whether toolset/SDK acquisition installs. -norestore must win so that skipping restore
+# also skips toolset initialization, not just the explicit Restore build below.
+if ($norestore) { $restore = $false }
+
+# sdk-task runs a standalone Arcade SDK task and does not need repo-specific toolset setup.
+# Skip importing configure-toolset.ps1 so its side effects (e.g. a repo's configure-toolset.ps1
+# calling exit) don't terminate this script before the task runs.
+$disableConfigureToolsetImport = $true
+
. $PSScriptRoot\tools.ps1
function Print-Usage() {
Write-Host "Common settings:"
- Write-Host " -task Name of Arcade task (name of a project in SdkTasks directory of the Arcade SDK package)"
- Write-Host " -restore Restore dependencies"
+ Write-Host " -task Name of Arcade task (name of a project in toolset directory of the Arcade SDK package)"
+ Write-Host " -restore (Legacy) Restore runs by default; retained for backward compatibility. Use -norestore to skip"
+ Write-Host " -norestore Skip restoring dependencies"
Write-Host " -verbosity Msbuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic]"
Write-Host " -help Print help and exit"
Write-Host ""
@@ -66,20 +79,7 @@ try {
if( $msbuildEngine -eq "vs") {
# Ensure desktop MSBuild is available for sdk tasks.
- if( -not ($GlobalJson.tools.PSObject.Properties.Name -contains "vs" )) {
- $GlobalJson.tools | Add-Member -Name "vs" -Value (ConvertFrom-Json "{ `"version`": `"16.5`" }") -MemberType NoteProperty
- }
- if( -not ($GlobalJson.tools.PSObject.Properties.Name -match "xcopy-msbuild" )) {
- $GlobalJson.tools | Add-Member -Name "xcopy-msbuild" -Value "18.0.0" -MemberType NoteProperty
- }
- if ($GlobalJson.tools."xcopy-msbuild".Trim() -ine "none") {
- $xcopyMSBuildToolsFolder = InitializeXCopyMSBuild $GlobalJson.tools."xcopy-msbuild" -install $true
- }
- if ($xcopyMSBuildToolsFolder -eq $null) {
- throw 'Unable to get xcopy downloadable version of msbuild'
- }
-
- $global:_MSBuildExe = "$($xcopyMSBuildToolsFolder)\MSBuild\Current\Bin\MSBuild.exe"
+ $global:_MSBuildExe = InitializeVisualStudioMSBuild
}
$taskProject = GetSdkTaskProject $task
diff --git a/eng/common/sdk-task.sh b/eng/common/sdk-task.sh
index 3270f83fa9a..a7f1ba060d7 100644
--- a/eng/common/sdk-task.sh
+++ b/eng/common/sdk-task.sh
@@ -2,8 +2,9 @@
show_usage() {
echo "Common settings:"
- echo " --task Name of Arcade task (name of a project in SdkTasks directory of the Arcade SDK package)"
- echo " --restore Restore dependencies"
+ echo " --task Name of Arcade task (name of a project in toolset directory of the Arcade SDK package)"
+ echo " --restore (Legacy) Restore runs by default; retained for backward compatibility. Use --norestore to skip"
+ echo " --norestore Skip restoring dependencies"
echo " --verbosity Msbuild verbosity: q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic]"
echo " --help Print help and exit"
echo ""
@@ -50,10 +51,11 @@ binary_log=true
configuration="Debug"
verbosity="minimal"
exclude_ci_binary_log=false
-restore=false
+# restore defaults to on; --restore is retained only so existing consumers that pass it don't break. Use --norestore to opt out.
+restore=true
help=false
properties=''
-warnAsError=true
+warn_as_error=true
while (($# > 0)); do
lowerI="$(echo $1 | tr "[:upper:]" "[:lower:]")"
@@ -63,7 +65,10 @@ while (($# > 0)); do
shift 2
;;
--restore)
- restore=true
+ shift 1
+ ;;
+ --norestore)
+ restore=false
shift 1
;;
--verbosity)
@@ -75,8 +80,8 @@ while (($# > 0)); do
exclude_ci_binary_log=true
shift 1
;;
- --noWarnAsError)
- warnAsError=false
+ --nowarnaserror)
+ warn_as_error=false
shift 1
;;
--help)
@@ -97,6 +102,11 @@ if $help; then
exit 0
fi
+# sdk-task runs a standalone Arcade SDK task and does not need repo-specific toolset setup.
+# Skip importing configure-toolset.sh so its side effects (e.g. a repo's configure-toolset.sh
+# calling exit) don't terminate this script before the task runs.
+disable_configure_toolset_import=1
+
. "$scriptroot/tools.sh"
InitializeToolset
diff --git a/eng/common/sdl/NuGet.config b/eng/common/sdl/NuGet.config
deleted file mode 100644
index 3849bdb3cf5..00000000000
--- a/eng/common/sdl/NuGet.config
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/eng/common/sdl/configure-sdl-tool.ps1 b/eng/common/sdl/configure-sdl-tool.ps1
deleted file mode 100644
index 27f5a4115fc..00000000000
--- a/eng/common/sdl/configure-sdl-tool.ps1
+++ /dev/null
@@ -1,130 +0,0 @@
-Param(
- [string] $GuardianCliLocation,
- [string] $WorkingDirectory,
- [string] $TargetDirectory,
- [string] $GdnFolder,
- # The list of Guardian tools to configure. For each object in the array:
- # - If the item is a [hashtable], it must contain these entries:
- # - Name = The tool name as Guardian knows it.
- # - Scenario = (Optional) Scenario-specific name for this configuration entry. It must be unique
- # among all tool entries with the same Name.
- # - Args = (Optional) Array of Guardian tool configuration args, like '@("Target > C:\temp")'
- # - If the item is a [string] $v, it is treated as '@{ Name="$v" }'
- [object[]] $ToolsList,
- [string] $GuardianLoggerLevel='Standard',
- # Optional: Additional params to add to any tool using CredScan.
- [string[]] $CrScanAdditionalRunConfigParams,
- # Optional: Additional params to add to any tool using PoliCheck.
- [string[]] $PoliCheckAdditionalRunConfigParams,
- # Optional: Additional params to add to any tool using CodeQL/Semmle.
- [string[]] $CodeQLAdditionalRunConfigParams,
- # Optional: Additional params to add to any tool using Binskim.
- [string[]] $BinskimAdditionalRunConfigParams
-)
-
-$ErrorActionPreference = 'Stop'
-Set-StrictMode -Version 2.0
-$disableConfigureToolsetImport = $true
-$global:LASTEXITCODE = 0
-
-try {
- # `tools.ps1` checks $ci to perform some actions. Since the SDL
- # scripts don't necessarily execute in the same agent that run the
- # build.ps1/sh script this variable isn't automatically set.
- $ci = $true
- . $PSScriptRoot\..\tools.ps1
-
- # Normalize tools list: all in [hashtable] form with defined values for each key.
- $ToolsList = $ToolsList |
- ForEach-Object {
- if ($_ -is [string]) {
- $_ = @{ Name = $_ }
- }
-
- if (-not ($_['Scenario'])) { $_.Scenario = "" }
- if (-not ($_['Args'])) { $_.Args = @() }
- $_
- }
-
- Write-Host "List of tools to configure:"
- $ToolsList | ForEach-Object { $_ | Out-String | Write-Host }
-
- # We store config files in the r directory of .gdn
- $gdnConfigPath = Join-Path $GdnFolder 'r'
- $ValidPath = Test-Path $GuardianCliLocation
-
- if ($ValidPath -eq $False)
- {
- Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Invalid Guardian CLI Location."
- ExitWithExitCode 1
- }
-
- foreach ($tool in $ToolsList) {
- # Put together the name and scenario to make a unique key.
- $toolConfigName = $tool.Name
- if ($tool.Scenario) {
- $toolConfigName += "_" + $tool.Scenario
- }
-
- Write-Host "=== Configuring $toolConfigName..."
-
- $gdnConfigFile = Join-Path $gdnConfigPath "$toolConfigName-configure.gdnconfig"
-
- # For some tools, add default and automatic args.
- switch -Exact ($tool.Name) {
- 'credscan' {
- if ($targetDirectory) {
- $tool.Args += "`"TargetDirectory < $TargetDirectory`""
- }
- $tool.Args += "`"OutputType < pre`""
- $tool.Args += $CrScanAdditionalRunConfigParams
- }
- 'policheck' {
- if ($targetDirectory) {
- $tool.Args += "`"Target < $TargetDirectory`""
- }
- $tool.Args += $PoliCheckAdditionalRunConfigParams
- }
- {$_ -in 'semmle', 'codeql'} {
- if ($targetDirectory) {
- $tool.Args += "`"SourceCodeDirectory < $TargetDirectory`""
- }
- $tool.Args += $CodeQLAdditionalRunConfigParams
- }
- 'binskim' {
- if ($targetDirectory) {
- # Binskim crashes due to specific PDBs. GitHub issue: https://github.com/microsoft/binskim/issues/924.
- # We are excluding all `_.pdb` files from the scan.
- $tool.Args += "`"Target < $TargetDirectory\**;-:file|$TargetDirectory\**\_.pdb`""
- }
- $tool.Args += $BinskimAdditionalRunConfigParams
- }
- }
-
- # Create variable pointing to the args array directly so we can use splat syntax later.
- $toolArgs = $tool.Args
-
- # Configure the tool. If args array is provided or the current tool has some default arguments
- # defined, add "--args" and splat each element on the end. Arg format is "{Arg id} < {Value}",
- # one per parameter. Doc page for "guardian configure":
- # https://dev.azure.com/securitytools/SecurityIntegration/_wiki/wikis/Guardian/1395/configure
- Exec-BlockVerbosely {
- & $GuardianCliLocation configure `
- --working-directory $WorkingDirectory `
- --tool $tool.Name `
- --output-path $gdnConfigFile `
- --logger-level $GuardianLoggerLevel `
- --noninteractive `
- --force `
- $(if ($toolArgs) { "--args" }) @toolArgs
- Exit-IfNZEC "Sdl"
- }
-
- Write-Host "Created '$toolConfigName' configuration file: $gdnConfigFile"
- }
-}
-catch {
- Write-Host $_.ScriptStackTrace
- Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_
- ExitWithExitCode 1
-}
diff --git a/eng/common/sdl/execute-all-sdl-tools.ps1 b/eng/common/sdl/execute-all-sdl-tools.ps1
deleted file mode 100644
index 4715d75e974..00000000000
--- a/eng/common/sdl/execute-all-sdl-tools.ps1
+++ /dev/null
@@ -1,167 +0,0 @@
-Param(
- [string] $GuardianPackageName, # Required: the name of guardian CLI package (not needed if GuardianCliLocation is specified)
- [string] $NugetPackageDirectory, # Required: directory where NuGet packages are installed (not needed if GuardianCliLocation is specified)
- [string] $GuardianCliLocation, # Optional: Direct location of Guardian CLI executable if GuardianPackageName & NugetPackageDirectory are not specified
- [string] $Repository=$env:BUILD_REPOSITORY_NAME, # Required: the name of the repository (e.g. dotnet/arcade)
- [string] $BranchName=$env:BUILD_SOURCEBRANCH, # Optional: name of branch or version of gdn settings; defaults to master
- [string] $SourceDirectory=$env:BUILD_SOURCESDIRECTORY, # Required: the directory where source files are located
- [string] $ArtifactsDirectory = (Join-Path $env:BUILD_ARTIFACTSTAGINGDIRECTORY ('artifacts')), # Required: the directory where build artifacts are located
- [string] $AzureDevOpsAccessToken, # Required: access token for dnceng; should be provided via KeyVault
-
- # Optional: list of SDL tools to run on source code. See 'configure-sdl-tool.ps1' for tools list
- # format.
- [object[]] $SourceToolsList,
- # Optional: list of SDL tools to run on built artifacts. See 'configure-sdl-tool.ps1' for tools
- # list format.
- [object[]] $ArtifactToolsList,
- # Optional: list of SDL tools to run without automatically specifying a target directory. See
- # 'configure-sdl-tool.ps1' for tools list format.
- [object[]] $CustomToolsList,
-
- [bool] $TsaPublish=$False, # Optional: true will publish results to TSA; only set to true after onboarding to TSA; TSA is the automated framework used to upload test results as bugs.
- [string] $TsaBranchName=$env:BUILD_SOURCEBRANCH, # Optional: required for TSA publish; defaults to $(Build.SourceBranchName); TSA is the automated framework used to upload test results as bugs.
- [string] $TsaRepositoryName=$env:BUILD_REPOSITORY_NAME, # Optional: TSA repository name; will be generated automatically if not submitted; TSA is the automated framework used to upload test results as bugs.
- [string] $BuildNumber=$env:BUILD_BUILDNUMBER, # Optional: required for TSA publish; defaults to $(Build.BuildNumber)
- [bool] $UpdateBaseline=$False, # Optional: if true, will update the baseline in the repository; should only be run after fixing any issues which need to be fixed
- [bool] $TsaOnboard=$False, # Optional: if true, will onboard the repository to TSA; should only be run once; TSA is the automated framework used to upload test results as bugs.
- [string] $TsaInstanceUrl, # Optional: only needed if TsaOnboard or TsaPublish is true; the instance-url registered with TSA; TSA is the automated framework used to upload test results as bugs.
- [string] $TsaCodebaseName, # Optional: only needed if TsaOnboard or TsaPublish is true; the name of the codebase registered with TSA; TSA is the automated framework used to upload test results as bugs.
- [string] $TsaProjectName, # Optional: only needed if TsaOnboard or TsaPublish is true; the name of the project registered with TSA; TSA is the automated framework used to upload test results as bugs.
- [string] $TsaNotificationEmail, # Optional: only needed if TsaOnboard is true; the email(s) which will receive notifications of TSA bug filings (e.g. alias@microsoft.com); TSA is the automated framework used to upload test results as bugs.
- [string] $TsaCodebaseAdmin, # Optional: only needed if TsaOnboard is true; the aliases which are admins of the TSA codebase (e.g. DOMAIN\alias); TSA is the automated framework used to upload test results as bugs.
- [string] $TsaBugAreaPath, # Optional: only needed if TsaOnboard is true; the area path where TSA will file bugs in AzDO; TSA is the automated framework used to upload test results as bugs.
- [string] $TsaIterationPath, # Optional: only needed if TsaOnboard is true; the iteration path where TSA will file bugs in AzDO; TSA is the automated framework used to upload test results as bugs.
- [string] $GuardianLoggerLevel='Standard', # Optional: the logger level for the Guardian CLI; options are Trace, Verbose, Standard, Warning, and Error
- [string[]] $CrScanAdditionalRunConfigParams, # Optional: Additional Params to custom build a CredScan run config in the format @("xyz:abc","sdf:1")
- [string[]] $PoliCheckAdditionalRunConfigParams, # Optional: Additional Params to custom build a Policheck run config in the format @("xyz:abc","sdf:1")
- [string[]] $CodeQLAdditionalRunConfigParams, # Optional: Additional Params to custom build a Semmle/CodeQL run config in the format @("xyz < abc","sdf < 1")
- [string[]] $BinskimAdditionalRunConfigParams, # Optional: Additional Params to custom build a Binskim run config in the format @("xyz < abc","sdf < 1")
- [bool] $BreakOnFailure=$False # Optional: Fail the build if there were errors during the run
-)
-
-try {
- $ErrorActionPreference = 'Stop'
- Set-StrictMode -Version 2.0
- $disableConfigureToolsetImport = $true
- $global:LASTEXITCODE = 0
-
- # `tools.ps1` checks $ci to perform some actions. Since the SDL
- # scripts don't necessarily execute in the same agent that run the
- # build.ps1/sh script this variable isn't automatically set.
- $ci = $true
- . $PSScriptRoot\..\tools.ps1
-
- #Replace repo names to the format of org/repo
- if (!($Repository.contains('/'))) {
- $RepoName = $Repository -replace '(.*?)-(.*)', '$1/$2';
- }
- else{
- $RepoName = $Repository;
- }
-
- if ($GuardianPackageName) {
- $guardianCliLocation = Join-Path $NugetPackageDirectory (Join-Path $GuardianPackageName (Join-Path 'tools' 'guardian.cmd'))
- } else {
- $guardianCliLocation = $GuardianCliLocation
- }
-
- $workingDirectory = (Split-Path $SourceDirectory -Parent)
- $ValidPath = Test-Path $guardianCliLocation
-
- if ($ValidPath -eq $False)
- {
- Write-PipelineTelemetryError -Force -Category 'Sdl' -Message 'Invalid Guardian CLI Location.'
- ExitWithExitCode 1
- }
-
- Exec-BlockVerbosely {
- & $(Join-Path $PSScriptRoot 'init-sdl.ps1') -GuardianCliLocation $guardianCliLocation -Repository $RepoName -BranchName $BranchName -WorkingDirectory $workingDirectory -AzureDevOpsAccessToken $AzureDevOpsAccessToken -GuardianLoggerLevel $GuardianLoggerLevel
- }
- $gdnFolder = Join-Path $workingDirectory '.gdn'
-
- if ($TsaOnboard) {
- if ($TsaCodebaseName -and $TsaNotificationEmail -and $TsaCodebaseAdmin -and $TsaBugAreaPath) {
- Exec-BlockVerbosely {
- & $guardianCliLocation tsa-onboard --codebase-name "$TsaCodebaseName" --notification-alias "$TsaNotificationEmail" --codebase-admin "$TsaCodebaseAdmin" --instance-url "$TsaInstanceUrl" --project-name "$TsaProjectName" --area-path "$TsaBugAreaPath" --iteration-path "$TsaIterationPath" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel
- }
- if ($LASTEXITCODE -ne 0) {
- Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Guardian tsa-onboard failed with exit code $LASTEXITCODE."
- ExitWithExitCode $LASTEXITCODE
- }
- } else {
- Write-PipelineTelemetryError -Force -Category 'Sdl' -Message 'Could not onboard to TSA -- not all required values ($TsaCodebaseName, $TsaNotificationEmail, $TsaCodebaseAdmin, $TsaBugAreaPath) were specified.'
- ExitWithExitCode 1
- }
- }
-
- # Configure a list of tools with a default target directory. Populates the ".gdn/r" directory.
- function Configure-ToolsList([object[]] $tools, [string] $targetDirectory) {
- if ($tools -and $tools.Count -gt 0) {
- Exec-BlockVerbosely {
- & $(Join-Path $PSScriptRoot 'configure-sdl-tool.ps1') `
- -GuardianCliLocation $guardianCliLocation `
- -WorkingDirectory $workingDirectory `
- -TargetDirectory $targetDirectory `
- -GdnFolder $gdnFolder `
- -ToolsList $tools `
- -AzureDevOpsAccessToken $AzureDevOpsAccessToken `
- -GuardianLoggerLevel $GuardianLoggerLevel `
- -CrScanAdditionalRunConfigParams $CrScanAdditionalRunConfigParams `
- -PoliCheckAdditionalRunConfigParams $PoliCheckAdditionalRunConfigParams `
- -CodeQLAdditionalRunConfigParams $CodeQLAdditionalRunConfigParams `
- -BinskimAdditionalRunConfigParams $BinskimAdditionalRunConfigParams
- if ($BreakOnFailure) {
- Exit-IfNZEC "Sdl"
- }
- }
- }
- }
-
- # Configure Artifact and Source tools with default Target directories.
- Configure-ToolsList $ArtifactToolsList $ArtifactsDirectory
- Configure-ToolsList $SourceToolsList $SourceDirectory
- # Configure custom tools with no default Target directory.
- Configure-ToolsList $CustomToolsList $null
-
- # At this point, all tools are configured in the ".gdn" directory. Run them all in a single call.
- # (If we used "run" multiple times, each run would overwrite data from earlier runs.)
- Exec-BlockVerbosely {
- & $(Join-Path $PSScriptRoot 'run-sdl.ps1') `
- -GuardianCliLocation $guardianCliLocation `
- -WorkingDirectory $SourceDirectory `
- -UpdateBaseline $UpdateBaseline `
- -GdnFolder $gdnFolder
- }
-
- if ($TsaPublish) {
- if ($TsaBranchName -and $BuildNumber) {
- if (-not $TsaRepositoryName) {
- $TsaRepositoryName = "$($Repository)-$($BranchName)"
- }
- Exec-BlockVerbosely {
- & $guardianCliLocation tsa-publish --all-tools --repository-name "$TsaRepositoryName" --branch-name "$TsaBranchName" --build-number "$BuildNumber" --onboard $True --codebase-name "$TsaCodebaseName" --notification-alias "$TsaNotificationEmail" --codebase-admin "$TsaCodebaseAdmin" --instance-url "$TsaInstanceUrl" --project-name "$TsaProjectName" --area-path "$TsaBugAreaPath" --iteration-path "$TsaIterationPath" --working-directory $workingDirectory --logger-level $GuardianLoggerLevel
- }
- if ($LASTEXITCODE -ne 0) {
- Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Guardian tsa-publish failed with exit code $LASTEXITCODE."
- ExitWithExitCode $LASTEXITCODE
- }
- } else {
- Write-PipelineTelemetryError -Force -Category 'Sdl' -Message 'Could not publish to TSA -- not all required values ($TsaBranchName, $BuildNumber) were specified.'
- ExitWithExitCode 1
- }
- }
-
- if ($BreakOnFailure) {
- Write-Host "Failing the build in case of breaking results..."
- Exec-BlockVerbosely {
- & $guardianCliLocation break --working-directory $workingDirectory --logger-level $GuardianLoggerLevel
- }
- } else {
- Write-Host "Letting the build pass even if there were breaking results..."
- }
-}
-catch {
- Write-Host $_.ScriptStackTrace
- Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_
- exit 1
-}
diff --git a/eng/common/sdl/extract-artifact-archives.ps1 b/eng/common/sdl/extract-artifact-archives.ps1
deleted file mode 100644
index 68da4fbf257..00000000000
--- a/eng/common/sdl/extract-artifact-archives.ps1
+++ /dev/null
@@ -1,63 +0,0 @@
-# This script looks for each archive file in a directory and extracts it into the target directory.
-# For example, the file "$InputPath/bin.tar.gz" extracts to "$ExtractPath/bin.tar.gz.extracted/**".
-# Uses the "tar" utility added to Windows 10 / Windows 2019 that supports tar.gz and zip.
-param(
- # Full path to directory where archives are stored.
- [Parameter(Mandatory=$true)][string] $InputPath,
- # Full path to directory to extract archives into. May be the same as $InputPath.
- [Parameter(Mandatory=$true)][string] $ExtractPath
-)
-
-$ErrorActionPreference = 'Stop'
-Set-StrictMode -Version 2.0
-
-$disableConfigureToolsetImport = $true
-
-try {
- # `tools.ps1` checks $ci to perform some actions. Since the SDL
- # scripts don't necessarily execute in the same agent that run the
- # build.ps1/sh script this variable isn't automatically set.
- $ci = $true
- . $PSScriptRoot\..\tools.ps1
-
- Measure-Command {
- $jobs = @()
-
- # Find archive files for non-Windows and Windows builds.
- $archiveFiles = @(
- Get-ChildItem (Join-Path $InputPath "*.tar.gz")
- Get-ChildItem (Join-Path $InputPath "*.zip")
- )
-
- foreach ($targzFile in $archiveFiles) {
- $jobs += Start-Job -ScriptBlock {
- $file = $using:targzFile
- $fileName = [System.IO.Path]::GetFileName($file)
- $extractDir = Join-Path $using:ExtractPath "$fileName.extracted"
-
- New-Item $extractDir -ItemType Directory -Force | Out-Null
-
- Write-Host "Extracting '$file' to '$extractDir'..."
-
- # Pipe errors to stdout to prevent PowerShell detecting them and quitting the job early.
- # This type of quit skips the catch, so we wouldn't be able to tell which file triggered the
- # error. Save output so it can be stored in the exception string along with context.
- $output = tar -xf $file -C $extractDir 2>&1
- # Handle NZEC manually rather than using Exit-IfNZEC: we are in a background job, so we
- # don't have access to the outer scope.
- if ($LASTEXITCODE -ne 0) {
- throw "Error extracting '$file': non-zero exit code ($LASTEXITCODE). Output: '$output'"
- }
-
- Write-Host "Extracted to $extractDir"
- }
- }
-
- Receive-Job $jobs -Wait
- }
-}
-catch {
- Write-Host $_
- Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_
- ExitWithExitCode 1
-}
diff --git a/eng/common/sdl/extract-artifact-packages.ps1 b/eng/common/sdl/extract-artifact-packages.ps1
deleted file mode 100644
index f031ed5b25e..00000000000
--- a/eng/common/sdl/extract-artifact-packages.ps1
+++ /dev/null
@@ -1,82 +0,0 @@
-param(
- [Parameter(Mandatory=$true)][string] $InputPath, # Full path to directory where artifact packages are stored
- [Parameter(Mandatory=$true)][string] $ExtractPath # Full path to directory where the packages will be extracted
-)
-
-$ErrorActionPreference = 'Stop'
-Set-StrictMode -Version 2.0
-
-$disableConfigureToolsetImport = $true
-
-function ExtractArtifacts {
- if (!(Test-Path $InputPath)) {
- Write-Host "Input Path does not exist: $InputPath"
- ExitWithExitCode 0
- }
- $Jobs = @()
- Get-ChildItem "$InputPath\*.nupkg" |
- ForEach-Object {
- $Jobs += Start-Job -ScriptBlock $ExtractPackage -ArgumentList $_.FullName
- }
-
- foreach ($Job in $Jobs) {
- Wait-Job -Id $Job.Id | Receive-Job
- }
-}
-
-try {
- # `tools.ps1` checks $ci to perform some actions. Since the SDL
- # scripts don't necessarily execute in the same agent that run the
- # build.ps1/sh script this variable isn't automatically set.
- $ci = $true
- . $PSScriptRoot\..\tools.ps1
-
- $ExtractPackage = {
- param(
- [string] $PackagePath # Full path to a NuGet package
- )
-
- if (!(Test-Path $PackagePath)) {
- Write-PipelineTelemetryError -Category 'Build' -Message "Input file does not exist: $PackagePath"
- ExitWithExitCode 1
- }
-
- $RelevantExtensions = @('.dll', '.exe', '.pdb')
- Write-Host -NoNewLine 'Extracting ' ([System.IO.Path]::GetFileName($PackagePath)) '...'
-
- $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath)
- $ExtractPath = Join-Path -Path $using:ExtractPath -ChildPath $PackageId
-
- Add-Type -AssemblyName System.IO.Compression.FileSystem
-
- [System.IO.Directory]::CreateDirectory($ExtractPath);
-
- try {
- $zip = [System.IO.Compression.ZipFile]::OpenRead($PackagePath)
-
- $zip.Entries |
- Where-Object {$RelevantExtensions -contains [System.IO.Path]::GetExtension($_.Name)} |
- ForEach-Object {
- $TargetPath = Join-Path -Path $ExtractPath -ChildPath (Split-Path -Path $_.FullName)
- [System.IO.Directory]::CreateDirectory($TargetPath);
-
- $TargetFile = Join-Path -Path $ExtractPath -ChildPath $_.FullName
- [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, $TargetFile)
- }
- }
- catch {
- Write-Host $_
- Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_
- ExitWithExitCode 1
- }
- finally {
- $zip.Dispose()
- }
- }
- Measure-Command { ExtractArtifacts }
-}
-catch {
- Write-Host $_
- Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_
- ExitWithExitCode 1
-}
diff --git a/eng/common/sdl/init-sdl.ps1 b/eng/common/sdl/init-sdl.ps1
deleted file mode 100644
index 3ac1d92b370..00000000000
--- a/eng/common/sdl/init-sdl.ps1
+++ /dev/null
@@ -1,55 +0,0 @@
-Param(
- [string] $GuardianCliLocation,
- [string] $Repository,
- [string] $BranchName='master',
- [string] $WorkingDirectory,
- [string] $AzureDevOpsAccessToken,
- [string] $GuardianLoggerLevel='Standard'
-)
-
-$ErrorActionPreference = 'Stop'
-Set-StrictMode -Version 2.0
-$disableConfigureToolsetImport = $true
-$global:LASTEXITCODE = 0
-
-# `tools.ps1` checks $ci to perform some actions. Since the SDL
-# scripts don't necessarily execute in the same agent that run the
-# build.ps1/sh script this variable isn't automatically set.
-$ci = $true
-. $PSScriptRoot\..\tools.ps1
-
-# Don't display the console progress UI - it's a huge perf hit
-$ProgressPreference = 'SilentlyContinue'
-
-# Construct basic auth from AzDO access token; construct URI to the repository's gdn folder stored in that repository; construct location of zip file
-$encodedPat = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$AzureDevOpsAccessToken"))
-$escapedRepository = [Uri]::EscapeDataString("/$Repository/$BranchName/.gdn")
-$uri = "https://dev.azure.com/dnceng/internal/_apis/git/repositories/sdl-tool-cfg/Items?path=$escapedRepository&versionDescriptor[versionOptions]=0&`$format=zip&api-version=5.0"
-$zipFile = "$WorkingDirectory/gdn.zip"
-
-Add-Type -AssemblyName System.IO.Compression.FileSystem
-$gdnFolder = (Join-Path $WorkingDirectory '.gdn')
-
-try {
- # if the folder does not exist, we'll do a guardian init and push it to the remote repository
- Write-Host 'Initializing Guardian...'
- Write-Host "$GuardianCliLocation init --working-directory $WorkingDirectory --logger-level $GuardianLoggerLevel"
- & $GuardianCliLocation init --working-directory $WorkingDirectory --logger-level $GuardianLoggerLevel
- if ($LASTEXITCODE -ne 0) {
- Write-PipelineTelemetryError -Force -Category 'Build' -Message "Guardian init failed with exit code $LASTEXITCODE."
- ExitWithExitCode $LASTEXITCODE
- }
- # We create the mainbaseline so it can be edited later
- Write-Host "$GuardianCliLocation baseline --working-directory $WorkingDirectory --name mainbaseline"
- & $GuardianCliLocation baseline --working-directory $WorkingDirectory --name mainbaseline
- if ($LASTEXITCODE -ne 0) {
- Write-PipelineTelemetryError -Force -Category 'Build' -Message "Guardian baseline failed with exit code $LASTEXITCODE."
- ExitWithExitCode $LASTEXITCODE
- }
- ExitWithExitCode 0
-}
-catch {
- Write-Host $_.ScriptStackTrace
- Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_
- ExitWithExitCode 1
-}
diff --git a/eng/common/sdl/packages.config b/eng/common/sdl/packages.config
deleted file mode 100644
index e5f543ea68c..00000000000
--- a/eng/common/sdl/packages.config
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/eng/common/sdl/run-sdl.ps1 b/eng/common/sdl/run-sdl.ps1
deleted file mode 100644
index 2eac8c78f10..00000000000
--- a/eng/common/sdl/run-sdl.ps1
+++ /dev/null
@@ -1,49 +0,0 @@
-Param(
- [string] $GuardianCliLocation,
- [string] $WorkingDirectory,
- [string] $GdnFolder,
- [string] $UpdateBaseline,
- [string] $GuardianLoggerLevel='Standard'
-)
-
-$ErrorActionPreference = 'Stop'
-Set-StrictMode -Version 2.0
-$disableConfigureToolsetImport = $true
-$global:LASTEXITCODE = 0
-
-try {
- # `tools.ps1` checks $ci to perform some actions. Since the SDL
- # scripts don't necessarily execute in the same agent that run the
- # build.ps1/sh script this variable isn't automatically set.
- $ci = $true
- . $PSScriptRoot\..\tools.ps1
-
- # We store config files in the r directory of .gdn
- $gdnConfigPath = Join-Path $GdnFolder 'r'
- $ValidPath = Test-Path $GuardianCliLocation
-
- if ($ValidPath -eq $False)
- {
- Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "Invalid Guardian CLI Location."
- ExitWithExitCode 1
- }
-
- $gdnConfigFiles = Get-ChildItem $gdnConfigPath -Recurse -Include '*.gdnconfig'
- Write-Host "Discovered Guardian config files:"
- $gdnConfigFiles | Out-String | Write-Host
-
- Exec-BlockVerbosely {
- & $GuardianCliLocation run `
- --working-directory $WorkingDirectory `
- --baseline mainbaseline `
- --update-baseline $UpdateBaseline `
- --logger-level $GuardianLoggerLevel `
- --config @gdnConfigFiles
- Exit-IfNZEC "Sdl"
- }
-}
-catch {
- Write-Host $_.ScriptStackTrace
- Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_
- ExitWithExitCode 1
-}
diff --git a/eng/common/sdl/sdl.ps1 b/eng/common/sdl/sdl.ps1
deleted file mode 100644
index 648c5068d7d..00000000000
--- a/eng/common/sdl/sdl.ps1
+++ /dev/null
@@ -1,38 +0,0 @@
-
-function Install-Gdn {
- param(
- [Parameter(Mandatory=$true)]
- [string]$Path,
-
- # If omitted, install the latest version of Guardian, otherwise install that specific version.
- [string]$Version
- )
-
- $ErrorActionPreference = 'Stop'
- Set-StrictMode -Version 2.0
- $disableConfigureToolsetImport = $true
- $global:LASTEXITCODE = 0
-
- # `tools.ps1` checks $ci to perform some actions. Since the SDL
- # scripts don't necessarily execute in the same agent that run the
- # build.ps1/sh script this variable isn't automatically set.
- $ci = $true
- . $PSScriptRoot\..\tools.ps1
-
- $argumentList = @("install", "Microsoft.Guardian.Cli", "-Source https://securitytools.pkgs.visualstudio.com/_packaging/Guardian/nuget/v3/index.json", "-OutputDirectory $Path", "-NonInteractive", "-NoCache")
-
- if ($Version) {
- $argumentList += "-Version $Version"
- }
-
- Start-Process nuget -Verbose -ArgumentList $argumentList -NoNewWindow -Wait
-
- $gdnCliPath = Get-ChildItem -Filter guardian.cmd -Recurse -Path $Path
-
- if (!$gdnCliPath)
- {
- Write-PipelineTelemetryError -Category 'Sdl' -Message 'Failure installing Guardian'
- }
-
- return $gdnCliPath.FullName
-}
\ No newline at end of file
diff --git a/eng/common/sdl/trim-assets-version.ps1 b/eng/common/sdl/trim-assets-version.ps1
deleted file mode 100644
index 0daa2a9e946..00000000000
--- a/eng/common/sdl/trim-assets-version.ps1
+++ /dev/null
@@ -1,75 +0,0 @@
-<#
-.SYNOPSIS
-Install and run the 'Microsoft.DotNet.VersionTools.Cli' tool with the 'trim-artifacts-version' command to trim the version from the NuGet assets file name.
-
-.PARAMETER InputPath
-Full path to directory where artifact packages are stored
-
-.PARAMETER Recursive
-Search for NuGet packages recursively
-
-#>
-
-Param(
- [string] $InputPath,
- [bool] $Recursive = $true
-)
-
-$CliToolName = "Microsoft.DotNet.VersionTools.Cli"
-
-function Install-VersionTools-Cli {
- param(
- [Parameter(Mandatory=$true)][string]$Version
- )
-
- Write-Host "Installing the package '$CliToolName' with a version of '$version' ..."
- $feed = "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json"
-
- $argumentList = @("tool", "install", "--local", "$CliToolName", "--add-source $feed", "--no-cache", "--version $Version", "--create-manifest-if-needed")
- Start-Process "$dotnet" -Verbose -ArgumentList $argumentList -NoNewWindow -Wait
-}
-
-# -------------------------------------------------------------------
-
-if (!(Test-Path $InputPath)) {
- Write-Host "Input Path '$InputPath' does not exist"
- ExitWithExitCode 1
-}
-
-$ErrorActionPreference = 'Stop'
-Set-StrictMode -Version 2.0
-
-$disableConfigureToolsetImport = $true
-$global:LASTEXITCODE = 0
-
-# `tools.ps1` checks $ci to perform some actions. Since the SDL
-# scripts don't necessarily execute in the same agent that run the
-# build.ps1/sh script this variable isn't automatically set.
-$ci = $true
-. $PSScriptRoot\..\tools.ps1
-
-try {
- $dotnetRoot = InitializeDotNetCli -install:$true
- $dotnet = "$dotnetRoot\dotnet.exe"
-
- $toolsetVersion = Read-ArcadeSdkVersion
- Install-VersionTools-Cli -Version $toolsetVersion
-
- $cliToolFound = (& "$dotnet" tool list --local | Where-Object {$_.Split(' ')[0] -eq $CliToolName})
- if ($null -eq $cliToolFound) {
- Write-PipelineTelemetryError -Force -Category 'Sdl' -Message "The '$CliToolName' tool is not installed."
- ExitWithExitCode 1
- }
-
- Exec-BlockVerbosely {
- & "$dotnet" $CliToolName trim-assets-version `
- --assets-path $InputPath `
- --recursive $Recursive
- Exit-IfNZEC "Sdl"
- }
-}
-catch {
- Write-Host $_
- Write-PipelineTelemetryError -Force -Category 'Sdl' -Message $_
- ExitWithExitCode 1
-}
diff --git a/eng/common/template-guidance.md b/eng/common/template-guidance.md
index e2b07a865f1..f772aa3d78f 100644
--- a/eng/common/template-guidance.md
+++ b/eng/common/template-guidance.md
@@ -71,7 +71,6 @@ eng\common\
source-build.yml (shim)
source-index-stage1.yml (shim)
jobs\
- codeql-build.yml (shim)
jobs.yml (shim)
source-build.yml (shim)
post-build\
@@ -88,7 +87,6 @@ eng\common\
source-build.yml (shim)
variables\
pool-providers.yml (logic + redirect) # templates/variables/pool-providers.yml will redirect to templates-official/variables/pool-providers.yml if you are running in the internal project
- sdl-variables.yml (logic)
core-templates\
job\
job.yml (logic)
@@ -97,7 +95,6 @@ eng\common\
source-build.yml (logic)
source-index-stage1.yml (logic)
jobs\
- codeql-build.yml (logic)
jobs.yml (logic)
source-build.yml (logic)
post-build\
diff --git a/eng/common/templates-official/jobs/codeql-build.yml b/eng/common/templates-official/jobs/codeql-build.yml
deleted file mode 100644
index a726322ecfe..00000000000
--- a/eng/common/templates-official/jobs/codeql-build.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-jobs:
-- template: /eng/common/core-templates/jobs/codeql-build.yml
- parameters:
- is1ESPipeline: true
-
- ${{ each parameter in parameters }}:
- ${{ parameter.key }}: ${{ parameter.value }}
diff --git a/eng/common/templates-official/variables/sdl-variables.yml b/eng/common/templates-official/variables/sdl-variables.yml
deleted file mode 100644
index f1311bbb1b3..00000000000
--- a/eng/common/templates-official/variables/sdl-variables.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-variables:
-# The Guardian version specified in 'eng/common/sdl/packages.config'. This value must be kept in
-# sync with the packages.config file.
-- name: DefaultGuardianVersion
- value: 0.109.0
-- name: GuardianPackagesConfigFile
- value: $(System.DefaultWorkingDirectory)\eng\common\sdl\packages.config
\ No newline at end of file
diff --git a/eng/common/templates/job/job.yml b/eng/common/templates/job/job.yml
index 5e261f34db4..85501406a54 100644
--- a/eng/common/templates/job/job.yml
+++ b/eng/common/templates/job/job.yml
@@ -21,11 +21,6 @@ jobs:
- ${{ each step in parameters.steps }}:
- ${{ step }}
- # we don't run CG in public
- - ${{ if eq(variables['System.TeamProject'], 'public') }}:
- - script: echo "##vso[task.setvariable variable=skipComponentGovernanceDetection]true"
- displayName: Set skipComponentGovernanceDetection variable
-
artifactPublishSteps:
- ${{ if ne(parameters.artifacts.publish, '') }}:
- ${{ if and(ne(parameters.artifacts.publish.artifacts, 'false'), ne(parameters.artifacts.publish.artifacts, '')) }}:
diff --git a/eng/common/templates/jobs/codeql-build.yml b/eng/common/templates/jobs/codeql-build.yml
deleted file mode 100644
index 517f24d6a52..00000000000
--- a/eng/common/templates/jobs/codeql-build.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-jobs:
-- template: /eng/common/core-templates/jobs/codeql-build.yml
- parameters:
- is1ESPipeline: false
-
- ${{ each parameter in parameters }}:
- ${{ parameter.key }}: ${{ parameter.value }}
diff --git a/eng/templates/regression-test-jobs.yml b/eng/templates/regression-test-jobs.yml
index 16da81059c2..ba7a3c19dab 100644
--- a/eng/templates/regression-test-jobs.yml
+++ b/eng/templates/regression-test-jobs.yml
@@ -141,6 +141,28 @@ jobs:
version: '10.0.100'
installationPath: $(Pipeline.Workspace)/TestRepo/.dotnet
+ # Install the SDK that built the compiler (version from global.json)
+ # into the regression test's .dotnet so fsc.dll can find the runtime.
+ # Tries default feed first, then ci.dot.net/public (same fallback as eng/common).
+ - pwsh: |
+ $v = (Get-Content "$(Build.SourcesDirectory)/global.json" | ConvertFrom-Json).tools.dotnet
+ $d = "$(Pipeline.Workspace)/TestRepo/.dotnet"
+ $u = "https://builds.dotnet.microsoft.com/dotnet/scripts/v1"
+ if ($IsWindows) {
+ Invoke-WebRequest "$u/dotnet-install.ps1" -OutFile "$d/dotnet-install.ps1"
+ & "$d/dotnet-install.ps1" -Version $v -InstallDir $d -SkipNonVersionedFiles
+ if ($LASTEXITCODE -ne 0) {
+ & "$d/dotnet-install.ps1" -Version $v -InstallDir $d -SkipNonVersionedFiles -AzureFeed "https://ci.dot.net/public"
+ }
+ } else {
+ Invoke-WebRequest "$u/dotnet-install.sh" -OutFile "$d/dotnet-install.sh"
+ chmod +x "$d/dotnet-install.sh"
+ bash "$d/dotnet-install.sh" --version $v --install-dir $d --skip-non-versioned-files ||
+ bash "$d/dotnet-install.sh" --version $v --install-dir $d --skip-non-versioned-files --azure-feed "https://ci.dot.net/public"
+ }
+ displayName: Install compiler SDK for ${{ item.displayName }}
+ continueOnError: true
+
- pwsh: |
Set-Location $(Pipeline.Workspace)/TestRepo
diff --git a/global.json b/global.json
index 53910226296..6f66064c9e7 100644
--- a/global.json
+++ b/global.json
@@ -1,7 +1,8 @@
{
"sdk": {
- "version": "10.0.301",
+ "version": "11.0.100-preview.6.26359.118",
"allowPrerelease": true,
+ "rollForward": "latestMinor",
"paths": [
".dotnet",
"$host$"
@@ -12,7 +13,7 @@
"runner": "Microsoft.Testing.Platform"
},
"tools": {
- "dotnet": "10.0.301",
+ "dotnet": "11.0.100-preview.6.26359.118",
"vs": {
"version": "18.0",
"components": [
@@ -22,7 +23,7 @@
"xcopy-msbuild": "18.0.0"
},
"msbuild-sdks": {
- "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.26379.2",
+ "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.26403.2",
"Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.23255.2"
}
}
diff --git a/setup/Swix/Directory.Build.props b/setup/Swix/Directory.Build.props
index 0a9e6f4ecc5..3e43aa310f4 100644
--- a/setup/Swix/Directory.Build.props
+++ b/setup/Swix/Directory.Build.props
@@ -1,6 +1,8 @@
+
+ falsetrueMicrosoft.FSharpneutral
diff --git a/src/Compiler/AbstractIL/EncMethodDebugInformation.fs b/src/Compiler/AbstractIL/EncMethodDebugInformation.fs
new file mode 100644
index 00000000000..e605b2208a4
--- /dev/null
+++ b/src/Compiler/AbstractIL/EncMethodDebugInformation.fs
@@ -0,0 +1,557 @@
+/// Edit-and-Continue method debug information blobs.
+///
+/// This module replicates, byte for byte, the three Portable-PDB CustomDebugInformation
+/// blob formats Roslyn persists per method to support Edit and Continue
+/// (roslyn/src/Compilers/Core/Portable/Emit/EditAndContinueMethodDebugInformation.cs):
+///
+/// - EnC Local Slot Map (kind 755F52A8-91C5-45BE-B4B8-209571E552BD)
+/// - EnC Lambda and Closure Map (kind A643004C-0240-496F-A783-30D64F4979DE)
+/// - EnC State Machine State Map (kind 8B78CD68-2EDE-420B-980B-E15884B8AAA3)
+///
+/// (GUIDs: roslyn/src/Dependencies/CodeAnalysis.Debugging/PortableCustomDebugInfoKinds.cs.)
+///
+/// All multi-byte integers use the ECMA-335 compressed unsigned/signed encodings via
+/// System.Reflection.Metadata's BlobBuilder.WriteCompressedInteger /
+/// WriteCompressedSignedInteger and BlobReader.ReadCompressedInteger /
+/// ReadCompressedSignedInteger, exactly as Roslyn writes/reads them.
+///
+/// Every "syntax offset" slot in these blobs is an opaque, caller-defined integer key
+/// (Roslyn: the syntax offset of the lambda/closure/state-machine-suspension syntax
+/// node). This module does not require the key to be a source offset; it only requires
+/// determinism across generations. tryEncodeOccurrenceKey/decodeOccurrenceKey provide one
+/// reusable way to pack a short (depth <= 2) ordinal chain into such a key.
+module internal FSharp.Compiler.AbstractIL.EncMethodDebugInformation
+
+#nowarn "9" // NativePtr: BlobReader only exposes a byte*-based constructor
+
+open System
+open System.Collections.Generic
+open System.Collections.Immutable
+open System.IO
+open System.Reflection.Metadata
+open System.Reflection.Metadata.Ecma335
+open System.Runtime.InteropServices
+open Microsoft.FSharp.NativeInterop
+
+/// Portable-PDB CustomDebugInformation kind GUIDs for the EnC blobs, copied verbatim
+/// from roslyn/src/Dependencies/CodeAnalysis.Debugging/PortableCustomDebugInfoKinds.cs.
+[]
+module PortableCustomDebugInfoKinds =
+
+ /// EnC Local Slot Map CDI kind.
+ let encLocalSlotMap = Guid("755F52A8-91C5-45BE-B4B8-209571E552BD")
+
+ /// EnC Lambda and Closure Map CDI kind.
+ let encLambdaAndClosureMap = Guid("A643004C-0240-496F-A783-30D64F4979DE")
+
+ /// EnC State Machine State Map CDI kind.
+ let encStateMachineStateMap = Guid("8B78CD68-2EDE-420B-980B-E15884B8AAA3")
+
+/// Closure ordinal of a lambda that is lowered to a static (non-capturing) method.
+/// Mirrors Roslyn's LambdaDebugInfo.StaticClosureOrdinal.
+[]
+let StaticClosureOrdinal = -1
+
+/// Closure ordinal of a lambda closed over the 'this' pointer only.
+/// Mirrors Roslyn's LambdaDebugInfo.ThisOnlyClosureOrdinal.
+[]
+let ThisOnlyClosureOrdinal = -2
+
+/// Smallest valid closure ordinal. Mirrors Roslyn's LambdaDebugInfo.MinClosureOrdinal.
+[]
+let MinClosureOrdinal = ThisOnlyClosureOrdinal
+
+/// Method ordinal of a method that has no lambda map (an empty blob decodes to this).
+/// Mirrors Roslyn's DebugId.UndefinedOrdinal.
+[]
+let UndefinedMethodOrdinal = -1
+
+/// Marker byte introducing the (optional) negative syntax-offset baseline in the
+/// local-slot-map blob. Mirrors Roslyn's SyntaxOffsetBaseline = 0xFF.
+[]
+let private SyntaxOffsetBaselineMarker = 0xFFuy
+
+/// Largest synthesized-local kind serializable in the slot map: the kind is stored as
+/// (kind + 1) in bits 0-5 of the leading byte (bit 6 is unused, bit 7 flags a trailing ordinal), and
+/// Roslyn's reader recovers it with mask 0x3F, so only kinds 0..0x3E round-trip.
+[]
+let MaxSerializableLocalKind = 0x3E
+
+/// One slot in the EnC Local Slot Map: the local variable layout of a method body,
+/// recorded so a later generation can map its locals onto the same slot indices.
+[]
+type EncLocalSlotInfo =
+ /// A short-lived lowering temp: serialized as the single byte 0x00, carrying no
+ /// identity (a later generation never reuses it).
+ | Temp
+
+ /// A long-lived synthesized local.
+ /// kind: synthesized-local kind (Roslyn SynthesizedLocalKind value, 0..MaxSerializableLocalKind;
+ /// 0 = user-defined local).
+ /// syntaxOffset: caller-defined key of the declaring occurrence
+ /// (Roslyn: syntax offset of the local's declarator).
+ /// ordinal: zero-based disambiguator among slots sharing the same kind and offset (>= 0).
+ | Slot of kind: int * syntaxOffset: int * ordinal: int
+
+/// One closure scope in the EnC Lambda and Closure Map. The closure's ordinal is its
+/// index in EncMethodDebugInformation.Closures; lambdas reference closures by that index.
+type EncClosureInfo =
+ {
+ /// Caller-defined key (Roslyn: syntax offset of the scope owning the closure).
+ SyntaxOffset: int
+ }
+
+/// One lambda in the EnC Lambda and Closure Map.
+type EncLambdaInfo =
+ {
+ /// Caller-defined key (Roslyn: syntax offset of the lambda body).
+ SyntaxOffset: int
+ /// Index into EncMethodDebugInformation.Closures of the closure holding the
+ /// lambda's captures, or StaticClosureOrdinal / ThisOnlyClosureOrdinal.
+ ClosureOrdinal: int
+ }
+
+/// One suspension point in the EnC State Machine State Map.
+type EncStateMachineStateInfo =
+ {
+ /// State machine state number assigned to the suspension point (may be negative:
+ /// Roslyn uses negative numbers for increasing-iteration finalize states).
+ StateNumber: int
+ /// Caller-defined key (Roslyn: syntax offset of the await/yield syntax node).
+ SyntaxOffset: int
+ }
+
+/// Debugging information associated with a method, persisted by the compiler in the
+/// Portable PDB to support Edit and Continue. Mirrors Roslyn's
+/// EditAndContinueMethodDebugInformation.
+type EncMethodDebugInformation =
+ {
+ /// Ordinal of the method within its generation (>= -1; UndefinedMethodOrdinal when absent).
+ MethodOrdinal: int
+ /// Local slot layout, in slot-index order (EnC Local Slot Map).
+ LocalSlots: EncLocalSlotInfo list
+ /// Closure scopes, in ordinal order (EnC Lambda and Closure Map).
+ Closures: EncClosureInfo list
+ /// Lambdas, in ordinal order (EnC Lambda and Closure Map).
+ Lambdas: EncLambdaInfo list
+ /// State machine suspension points (EnC State Machine State Map).
+ StateMachineStates: EncStateMachineStateInfo list
+ }
+
+ /// An empty map (no slots, lambdas, closures or states; undefined method ordinal).
+ static member Empty =
+ {
+ MethodOrdinal = UndefinedMethodOrdinal
+ LocalSlots = []
+ Closures = []
+ Lambdas = []
+ StateMachineStates = []
+ }
+
+// ---------------------------------------------------------------------------
+// Occurrence-key packing
+// ---------------------------------------------------------------------------
+
+/// Maximum encodable occurrence ordinal: each chain segment is 16 bits.
+[]
+let private MaxOccurrenceSegment = 0xFFFF
+
+/// Compressed unsigned integers must lie in [0, 0x1FFFFFFF); after baseline adjustment
+/// the serialized value is (key - baseline) with baseline <= -1, so keys must stay
+/// strictly below 0x1FFFFFFF - 1 to be writable. Cap at 29 bits minus the adjustment.
+[]
+let private MaxOccurrenceKey = 0x1FFFFFFD
+
+/// Packs an ordinal chain (root-first enclosing ordinals, ending with the innermost
+/// ordinal) into a deterministic int suitable for a "syntax offset" blob slot. Packing:
+/// 16-bit segments, least-significant segment = the innermost ordinal; an enclosing
+/// ordinal p is stored as (p + 1) shifted left 16 so that depth-1 keys (< 0x10000) and
+/// depth-2 keys (>= 0x10000) never collide. Fails closed (None) past the limits: chains
+/// deeper than 2, ordinals > 0xFFFF, or keys exceeding the compressed-integer budget —
+/// callers must then treat the chain as unmappable, never truncate.
+let tryEncodeOccurrenceKey (ordinalChain: int list) : int option =
+ match ordinalChain with
+ | [ ordinal ] when ordinal >= 0 && ordinal <= MaxOccurrenceSegment -> Some ordinal
+ | [ parent; ordinal ] when
+ parent >= 0
+ && ordinal >= 0
+ && ordinal <= MaxOccurrenceSegment
+ && parent < MaxOccurrenceSegment
+ ->
+ // Pack in int64: a large parent (e.g. 0xFFFE) would wrap ((parent + 1) <<< 16) negative in
+ // int32 and a negative key slips past the <= MaxOccurrenceKey bound, failing OPEN.
+ let key = ((int64 parent + 1L) <<< 16) ||| int64 ordinal
+
+ if key <= int64 MaxOccurrenceKey then
+ Some(int key)
+ else
+ None
+ | _ -> None
+
+/// Unpacks an occurrence key produced by tryEncodeOccurrenceKey back into its
+/// root-first ordinal chain.
+let decodeOccurrenceKey (key: int) : int list =
+ if key < 0 then
+ invalidArg (nameof key) $"occurrence key must be non-negative, got %d{key}"
+ elif key <= MaxOccurrenceSegment then
+ [ key ]
+ else
+ [ (key >>> 16) - 1; key &&& MaxOccurrenceSegment ]
+
+// ---------------------------------------------------------------------------
+// Blob helpers
+// ---------------------------------------------------------------------------
+
+let private invalidData (blobName: string) (offset: int) =
+ raise (InvalidDataException $"invalid EnC %s{blobName} blob: unexpected data at offset %d{offset}")
+
+// Absent CDI rows arrive as null at runtime even though the parameter is non-null in the
+// nullness model, so guard with box (FS3261-safe) rather than dropping the check.
+let private isEmpty (blob: byte[]) = isNull (box blob) || blob.Length = 0
+
+// ---------------------------------------------------------------------------
+// EnC Local Slot Map
+// Format (EditAndContinueMethodDebugInformation.cs, SerializeLocalSlots lines 145-191,
+// UncompressSlotMap lines 92-143): optional baseline record [0xFF, compressed(-baseline)],
+// then one record per slot: 0x00 for a temp, otherwise a leading byte with bits 0-5 =
+// kind + 1 and bit 7 = has-ordinal flag, followed by compressed(syntaxOffset - baseline)
+// and, when flagged, compressed(ordinal).
+// ---------------------------------------------------------------------------
+
+/// Serializes the EnC Local Slot Map blob for 'info', byte-for-byte as Roslyn's
+/// SerializeLocalSlots. Returns the empty array when there are no slots (no CDI row
+/// should be emitted then).
+let serializeLocalSlots (info: EncMethodDebugInformation) : byte[] =
+ match info.LocalSlots with
+ | [] -> Array.empty
+ | slots ->
+ let builder = BlobBuilder()
+
+ // The baseline is the most negative syntax offset, or -1 when none is negative
+ // (Roslyn lines 147-160). Offsets are stored relative to it so the common
+ // all-non-negative case costs no baseline record.
+ let syntaxOffsetBaseline =
+ (-1, slots)
+ ||> List.fold (fun acc slot ->
+ match slot with
+ | EncLocalSlotInfo.Temp -> acc
+ | EncLocalSlotInfo.Slot(_, syntaxOffset, _) -> min acc syntaxOffset)
+
+ if syntaxOffsetBaseline <> -1 then
+ builder.WriteByte SyntaxOffsetBaselineMarker
+ builder.WriteCompressedInteger(-syntaxOffsetBaseline)
+
+ for slot in slots do
+ match slot with
+ | EncLocalSlotInfo.Temp -> builder.WriteByte 0uy
+ | EncLocalSlotInfo.Slot(kind, syntaxOffset, ordinal) ->
+ if kind < 0 || kind > MaxSerializableLocalKind then
+ invalidArg (nameof info) $"local slot kind %d{kind} is outside the serializable range 0..%d{MaxSerializableLocalKind}"
+
+ if ordinal < 0 then
+ invalidArg (nameof info) $"local slot ordinal must be non-negative, got %d{ordinal}"
+
+ let hasOrdinal = ordinal > 0
+ let b = byte (kind + 1) ||| (if hasOrdinal then 0x80uy else 0uy)
+ builder.WriteByte b
+ builder.WriteCompressedInteger(syntaxOffset - syntaxOffsetBaseline)
+
+ if hasOrdinal then
+ builder.WriteCompressedInteger ordinal
+
+ builder.ToArray()
+
+/// Deserializes an EnC Local Slot Map blob, byte-for-byte as Roslyn's UncompressSlotMap.
+/// An empty (or null) blob yields no slots.
+let deserializeLocalSlots (blob: byte[]) : EncLocalSlotInfo list =
+ if isEmpty blob then
+ []
+ else
+ let handle = GCHandle.Alloc(blob, GCHandleType.Pinned)
+
+ try
+ let mutable reader =
+ BlobReader(NativePtr.ofNativeInt (handle.AddrOfPinnedObject()), blob.Length)
+
+ let slots = ResizeArray()
+ let mutable syntaxOffsetBaseline = -1
+
+ try
+ while reader.RemainingBytes > 0 do
+ let b = reader.ReadByte()
+
+ if b = SyntaxOffsetBaselineMarker then
+ syntaxOffsetBaseline <- -reader.ReadCompressedInteger()
+ elif b = 0uy then
+ slots.Add EncLocalSlotInfo.Temp
+ else
+ // Roslyn recovers the kind with mask 0x3F (line 126); bit 7 flags
+ // a trailing ordinal, bit 6 is unused by the writer.
+ let kind = int (b &&& 0x3Fuy) - 1
+ let hasOrdinal = b &&& 0x80uy <> 0uy
+ let syntaxOffset = reader.ReadCompressedInteger() + syntaxOffsetBaseline
+ let ordinal = if hasOrdinal then reader.ReadCompressedInteger() else 0
+ slots.Add(EncLocalSlotInfo.Slot(kind, syntaxOffset, ordinal))
+ with :? BadImageFormatException ->
+ invalidData "local slot map" reader.Offset
+
+ List.ofSeq slots
+ finally
+ handle.Free()
+
+// ---------------------------------------------------------------------------
+// EnC Lambda and Closure Map
+// Format (SerializeLambdaMap lines 261-302, UncompressLambdaMap lines 197-259):
+// compressed(methodOrdinal + 1), compressed(-baseline), compressed(closureCount),
+// closureCount * compressed(syntaxOffset - baseline), then until the blob ends:
+// [compressed(syntaxOffset - baseline), compressed(closureOrdinal - MinClosureOrdinal)]
+// per lambda.
+// ---------------------------------------------------------------------------
+
+/// Serializes the EnC Lambda and Closure Map blob for 'info', byte-for-byte as Roslyn's
+/// SerializeLambdaMap. Returns the empty array when there are no lambdas and no closures
+/// (Roslyn's MetadataWriter skips the CDI row in that case; note the method ordinal is
+/// then not persisted and decodes back as UndefinedMethodOrdinal).
+let serializeLambdaMap (info: EncMethodDebugInformation) : byte[] =
+ match info.Closures, info.Lambdas with
+ | [], [] -> Array.empty
+ | closures, lambdas ->
+ if info.MethodOrdinal < -1 then
+ invalidArg (nameof info) $"method ordinal must be >= -1, got %d{info.MethodOrdinal}"
+
+ let builder = BlobBuilder()
+ builder.WriteCompressedInteger(info.MethodOrdinal + 1)
+
+ // Negative offsets are rare (Roslyn: field/property initializers), so the
+ // baseline is -1 unless a smaller offset exists (Roslyn lines 266-286).
+ let syntaxOffsetBaseline =
+ let closureMin = (-1, closures) ||> List.fold (fun acc c -> min acc c.SyntaxOffset)
+ (closureMin, lambdas) ||> List.fold (fun acc l -> min acc l.SyntaxOffset)
+
+ builder.WriteCompressedInteger(-syntaxOffsetBaseline)
+ builder.WriteCompressedInteger closures.Length
+
+ for closure in closures do
+ builder.WriteCompressedInteger(closure.SyntaxOffset - syntaxOffsetBaseline)
+
+ for lambda in lambdas do
+ if
+ lambda.ClosureOrdinal < MinClosureOrdinal
+ || lambda.ClosureOrdinal >= closures.Length
+ then
+ invalidArg
+ (nameof info)
+ $"lambda closure ordinal %d{lambda.ClosureOrdinal} is outside [%d{MinClosureOrdinal}, %d{closures.Length})"
+
+ builder.WriteCompressedInteger(lambda.SyntaxOffset - syntaxOffsetBaseline)
+ builder.WriteCompressedInteger(lambda.ClosureOrdinal - MinClosureOrdinal)
+
+ builder.ToArray()
+
+/// Deserializes an EnC Lambda and Closure Map blob, byte-for-byte as Roslyn's
+/// UncompressLambdaMap. An empty (or null) blob yields (UndefinedMethodOrdinal, [], []).
+let deserializeLambdaMap (blob: byte[]) : int * EncClosureInfo list * EncLambdaInfo list =
+ if isEmpty blob then
+ UndefinedMethodOrdinal, [], []
+ else
+ let handle = GCHandle.Alloc(blob, GCHandleType.Pinned)
+
+ try
+ let mutable reader =
+ BlobReader(NativePtr.ofNativeInt (handle.AddrOfPinnedObject()), blob.Length)
+
+ let closures = ResizeArray()
+ let lambdas = ResizeArray()
+ let mutable methodOrdinal = UndefinedMethodOrdinal
+
+ try
+ methodOrdinal <- reader.ReadCompressedInteger() - 1
+ let syntaxOffsetBaseline = -reader.ReadCompressedInteger()
+ let closureCount = reader.ReadCompressedInteger()
+
+ for _ in 1..closureCount do
+ let syntaxOffset = reader.ReadCompressedInteger() + syntaxOffsetBaseline
+ closures.Add { SyntaxOffset = syntaxOffset }
+
+ while reader.RemainingBytes > 0 do
+ let syntaxOffset = reader.ReadCompressedInteger() + syntaxOffsetBaseline
+ let closureOrdinal = reader.ReadCompressedInteger() + MinClosureOrdinal
+
+ if closureOrdinal >= closureCount then
+ invalidData "lambda map" reader.Offset
+
+ lambdas.Add
+ {
+ SyntaxOffset = syntaxOffset
+ ClosureOrdinal = closureOrdinal
+ }
+ with :? BadImageFormatException ->
+ invalidData "lambda map" reader.Offset
+
+ methodOrdinal, List.ofSeq closures, List.ofSeq lambdas
+ finally
+ handle.Free()
+
+// ---------------------------------------------------------------------------
+// EnC State Machine State Map
+// Format (SerializeStateMachineStates lines 364-381, UncompressStateMachineStates
+// lines 309-362): compressed(count); when count > 0: compressed(-baseline) followed by
+// count * [compressedSigned(stateNumber), compressed(syntaxOffset - baseline)], entries
+// ordered by syntax offset.
+// ---------------------------------------------------------------------------
+
+/// Serializes the EnC State Machine State Map blob for 'info', byte-for-byte as
+/// Roslyn's SerializeStateMachineStates: entries are sorted by syntax offset (stably,
+/// preserving relative order of equal offsets, which encodes the per-offset relative
+/// ordinal). Returns the empty array when there are no states (no CDI row then).
+let serializeStateMachineStates (info: EncMethodDebugInformation) : byte[] =
+ match info.StateMachineStates with
+ | [] -> Array.empty
+ | states ->
+ let builder = BlobBuilder()
+ builder.WriteCompressedInteger states.Length
+
+ // Unlike the other two blobs the baseline here is min(minOffset, 0)
+ // (Roslyn line 372).
+ let syntaxOffsetBaseline =
+ min (states |> List.map (fun s -> s.SyntaxOffset) |> List.min) 0
+
+ builder.WriteCompressedInteger(-syntaxOffsetBaseline)
+
+ // Roslyn's reader rejects more than 256 entries sharing one syntax offset
+ // (relative ordinal must fit a byte, line 344); fail closed at write time.
+ for _, group in states |> List.groupBy (fun s -> s.SyntaxOffset) do
+ if group.Length > 256 then
+ invalidArg (nameof info) $"more than 256 state machine states share syntax offset %d{group.Head.SyntaxOffset}"
+
+ for state in states |> List.sortBy (fun s -> s.SyntaxOffset) do
+ builder.WriteCompressedSignedInteger state.StateNumber
+ builder.WriteCompressedInteger(state.SyntaxOffset - syntaxOffsetBaseline)
+
+ builder.ToArray()
+
+/// Deserializes an EnC State Machine State Map blob, byte-for-byte as Roslyn's
+/// UncompressStateMachineStates (including the ordered-by-offset and <= 256-per-offset
+/// validations). An empty (or null) blob yields no states.
+let deserializeStateMachineStates (blob: byte[]) : EncStateMachineStateInfo list =
+ if isEmpty blob then
+ []
+ else
+ let handle = GCHandle.Alloc(blob, GCHandleType.Pinned)
+
+ try
+ let mutable reader =
+ BlobReader(NativePtr.ofNativeInt (handle.AddrOfPinnedObject()), blob.Length)
+
+ let states = ResizeArray()
+
+ try
+ let count = reader.ReadCompressedInteger()
+
+ if count > 0 then
+ let syntaxOffsetBaseline = -reader.ReadCompressedInteger()
+ let mutable lastSyntaxOffset = Int32.MinValue
+ let mutable relativeOrdinal = 0
+
+ for _ in 1..count do
+ let stateNumber = reader.ReadCompressedSignedInteger()
+ let syntaxOffset = syntaxOffsetBaseline + reader.ReadCompressedInteger()
+
+ // Entries must be ordered by syntax offset and at most 256 may
+ // share one offset (Roslyn lines 336-347).
+ if syntaxOffset < lastSyntaxOffset then
+ invalidData "state machine state map" reader.Offset
+
+ relativeOrdinal <-
+ if syntaxOffset = lastSyntaxOffset then
+ relativeOrdinal + 1
+ else
+ 0
+
+ if relativeOrdinal > 255 then
+ invalidData "state machine state map" reader.Offset
+
+ states.Add
+ {
+ StateNumber = stateNumber
+ SyntaxOffset = syntaxOffset
+ }
+
+ lastSyntaxOffset <- syntaxOffset
+ with :? BadImageFormatException ->
+ invalidData "state machine state map" reader.Offset
+
+ List.ofSeq states
+ finally
+ handle.Free()
+
+/// Deserializes EnC method debug information from the three blobs (any of which may be
+/// null or empty). Mirrors Roslyn's EditAndContinueMethodDebugInformation.Create.
+let deserialize (slotMapBlob: byte[]) (lambdaMapBlob: byte[]) (stateMachineStateMapBlob: byte[]) : EncMethodDebugInformation =
+ let methodOrdinal, closures, lambdas = deserializeLambdaMap lambdaMapBlob
+
+ {
+ MethodOrdinal = methodOrdinal
+ LocalSlots = deserializeLocalSlots slotMapBlob
+ Closures = closures
+ Lambdas = lambdas
+ StateMachineStates = deserializeStateMachineStates stateMachineStateMapBlob
+ }
+
+/// Decodes every method-level EnC CustomDebugInformation row of a portable PDB image into
+/// per-method EnC debug information, keyed by MethodDef token (0x06xxxxxx). The CDI parent
+/// of the EnC rows is always a MethodDef handle, so token keying is unambiguous.
+/// Fail safe: a null/empty or non-PDB image yields the empty map, and a method whose
+/// blobs do not decode is omitted rather than guessed.
+let readEncMethodDebugInfoFromPortablePdb (pdbBytes: byte[]) : Map =
+ if isEmpty pdbBytes then
+ Map.empty
+ else
+ try
+ use provider =
+ MetadataReaderProvider.FromPortablePdbImage(ImmutableArray.CreateRange pdbBytes)
+
+ let reader = provider.GetMetadataReader()
+
+ let slotMapBlobs = Dictionary()
+ let lambdaMapBlobs = Dictionary()
+ let stateMapBlobs = Dictionary()
+
+ for cdiHandle in reader.CustomDebugInformation do
+ let cdi = reader.GetCustomDebugInformation cdiHandle
+
+ if cdi.Parent.Kind = HandleKind.MethodDefinition then
+ let methodToken = MetadataTokens.GetToken cdi.Parent
+ let kind = reader.GetGuid cdi.Kind
+
+ if kind = PortableCustomDebugInfoKinds.encLocalSlotMap then
+ slotMapBlobs[methodToken] <- reader.GetBlobBytes cdi.Value
+ elif kind = PortableCustomDebugInfoKinds.encLambdaAndClosureMap then
+ lambdaMapBlobs[methodToken] <- reader.GetBlobBytes cdi.Value
+ elif kind = PortableCustomDebugInfoKinds.encStateMachineStateMap then
+ stateMapBlobs[methodToken] <- reader.GetBlobBytes cdi.Value
+
+ let methodTokens =
+ Seq.concat [ slotMapBlobs.Keys :> seq; lambdaMapBlobs.Keys; stateMapBlobs.Keys ]
+ |> Seq.distinct
+
+ let tryBlob (blobs: Dictionary) token =
+ match blobs.TryGetValue token with
+ | true, blob -> blob
+ | _ -> Array.empty
+
+ (Map.empty, methodTokens)
+ ||> Seq.fold (fun acc token ->
+ try
+ let info =
+ deserialize (tryBlob slotMapBlobs token) (tryBlob lambdaMapBlobs token) (tryBlob stateMapBlobs token)
+
+ Map.add token info acc
+ with :? InvalidDataException ->
+ // Fail closed per method: an undecodable blob never yields a partial
+ // (and so potentially mismatched) map for its method.
+ acc)
+ with :? BadImageFormatException ->
+ // Not a portable PDB image (or a corrupted one): callers still get an empty
+ // map instead of a crash.
+ Map.empty
diff --git a/src/Compiler/AbstractIL/EncMethodDebugInformation.fsi b/src/Compiler/AbstractIL/EncMethodDebugInformation.fsi
new file mode 100644
index 00000000000..1e2ba76e7c8
--- /dev/null
+++ b/src/Compiler/AbstractIL/EncMethodDebugInformation.fsi
@@ -0,0 +1,178 @@
+/// Edit-and-Continue method debug information blobs.
+///
+/// This module replicates, byte for byte, the three Portable-PDB CustomDebugInformation
+/// blob formats Roslyn persists per method to support Edit and Continue
+/// (roslyn/src/Compilers/Core/Portable/Emit/EditAndContinueMethodDebugInformation.cs):
+///
+/// - EnC Local Slot Map (kind 755F52A8-91C5-45BE-B4B8-209571E552BD)
+/// - EnC Lambda and Closure Map (kind A643004C-0240-496F-A783-30D64F4979DE)
+/// - EnC State Machine State Map (kind 8B78CD68-2EDE-420B-980B-E15884B8AAA3)
+///
+/// (GUIDs: roslyn/src/Dependencies/CodeAnalysis.Debugging/PortableCustomDebugInfoKinds.cs.)
+///
+/// All multi-byte integers use the ECMA-335 compressed unsigned/signed encodings via
+/// System.Reflection.Metadata's BlobBuilder.WriteCompressedInteger /
+/// WriteCompressedSignedInteger and BlobReader.ReadCompressedInteger /
+/// ReadCompressedSignedInteger, exactly as Roslyn writes/reads them.
+///
+/// Every "syntax offset" slot in these blobs is an opaque, caller-defined integer key
+/// (Roslyn: the syntax offset of the lambda/closure/state-machine-suspension syntax
+/// node). This module does not require the key to be a source offset; it only requires
+/// determinism across generations. tryEncodeOccurrenceKey/decodeOccurrenceKey provide one
+/// reusable way to pack a short (depth <= 2) ordinal chain into such a key.
+module internal FSharp.Compiler.AbstractIL.EncMethodDebugInformation
+
+/// Portable-PDB CustomDebugInformation kind GUIDs for the EnC blobs, copied verbatim
+/// from roslyn/src/Dependencies/CodeAnalysis.Debugging/PortableCustomDebugInfoKinds.cs.
+[]
+module PortableCustomDebugInfoKinds =
+
+ /// EnC Local Slot Map CDI kind.
+ val encLocalSlotMap: System.Guid
+
+ /// EnC Lambda and Closure Map CDI kind.
+ val encLambdaAndClosureMap: System.Guid
+
+ /// EnC State Machine State Map CDI kind.
+ val encStateMachineStateMap: System.Guid
+
+/// Closure ordinal of a lambda that is lowered to a static (non-capturing) method.
+/// Mirrors Roslyn's LambdaDebugInfo.StaticClosureOrdinal.
+[]
+val StaticClosureOrdinal: int = -1
+
+/// Closure ordinal of a lambda closed over the 'this' pointer only.
+/// Mirrors Roslyn's LambdaDebugInfo.ThisOnlyClosureOrdinal.
+[]
+val ThisOnlyClosureOrdinal: int = -2
+
+/// Smallest valid closure ordinal. Mirrors Roslyn's LambdaDebugInfo.MinClosureOrdinal.
+[]
+val MinClosureOrdinal: int = -2
+
+/// Method ordinal of a method that has no lambda map (an empty blob decodes to this).
+/// Mirrors Roslyn's DebugId.UndefinedOrdinal.
+[]
+val UndefinedMethodOrdinal: int = -1
+
+/// Largest synthesized-local kind serializable in the slot map: the kind is stored as
+/// (kind + 1) in bits 0-5 of the leading byte (bit 6 is unused, bit 7 flags a trailing ordinal), and
+/// Roslyn's reader recovers it with mask 0x3F, so only kinds 0..0x3E round-trip.
+[]
+val MaxSerializableLocalKind: int = 0x3E
+
+/// One slot in the EnC Local Slot Map: the local variable layout of a method body,
+/// recorded so a later generation can map its locals onto the same slot indices.
+[]
+type EncLocalSlotInfo =
+ /// A short-lived lowering temp: serialized as the single byte 0x00, carrying no
+ /// identity (a later generation never reuses it).
+ | Temp
+
+ /// A long-lived synthesized local.
+ /// kind: synthesized-local kind (Roslyn SynthesizedLocalKind value, 0..MaxSerializableLocalKind;
+ /// 0 = user-defined local).
+ /// syntaxOffset: caller-defined key of the declaring occurrence
+ /// (Roslyn: syntax offset of the local's declarator).
+ /// ordinal: zero-based disambiguator among slots sharing the same kind and offset (>= 0).
+ | Slot of kind: int * syntaxOffset: int * ordinal: int
+
+/// One closure scope in the EnC Lambda and Closure Map. The closure's ordinal is its
+/// index in EncMethodDebugInformation.Closures; lambdas reference closures by that index.
+type EncClosureInfo =
+ {
+ /// Caller-defined key (Roslyn: syntax offset of the scope owning the closure).
+ SyntaxOffset: int
+ }
+
+/// One lambda in the EnC Lambda and Closure Map.
+type EncLambdaInfo =
+ {
+ /// Caller-defined key (Roslyn: syntax offset of the lambda body).
+ SyntaxOffset: int
+ /// Index into EncMethodDebugInformation.Closures of the closure holding the
+ /// lambda's captures, or StaticClosureOrdinal / ThisOnlyClosureOrdinal.
+ ClosureOrdinal: int
+ }
+
+/// One suspension point in the EnC State Machine State Map.
+type EncStateMachineStateInfo =
+ {
+ /// State machine state number assigned to the suspension point (may be negative:
+ /// Roslyn uses negative numbers for increasing-iteration finalize states).
+ StateNumber: int
+ /// Caller-defined key (Roslyn: syntax offset of the await/yield syntax node).
+ SyntaxOffset: int
+ }
+
+/// Debugging information associated with a method, persisted by the compiler in the
+/// Portable PDB to support Edit and Continue. Mirrors Roslyn's
+/// EditAndContinueMethodDebugInformation.
+type EncMethodDebugInformation =
+ {
+ /// Ordinal of the method within its generation (>= -1; UndefinedMethodOrdinal when absent).
+ MethodOrdinal: int
+ /// Local slot layout, in slot-index order (EnC Local Slot Map).
+ LocalSlots: EncLocalSlotInfo list
+ /// Closure scopes, in ordinal order (EnC Lambda and Closure Map).
+ Closures: EncClosureInfo list
+ /// Lambdas, in ordinal order (EnC Lambda and Closure Map).
+ Lambdas: EncLambdaInfo list
+ /// State machine suspension points (EnC State Machine State Map).
+ StateMachineStates: EncStateMachineStateInfo list
+ }
+
+ /// An empty map (no slots, lambdas, closures or states; undefined method ordinal).
+ static member Empty: EncMethodDebugInformation
+
+/// Packs an ordinal chain (root-first enclosing ordinals, ending with the innermost
+/// ordinal) into a deterministic int suitable for a "syntax offset" blob slot. Fails
+/// closed (None) past the limits: chains deeper than 2, ordinals > 0xFFFF, or keys
+/// exceeding the compressed-integer budget.
+val tryEncodeOccurrenceKey: ordinalChain: int list -> int option
+
+/// Unpacks an occurrence key produced by tryEncodeOccurrenceKey back into its
+/// root-first ordinal chain.
+val decodeOccurrenceKey: key: int -> int list
+
+/// Serializes the EnC Local Slot Map blob for 'info', byte-for-byte as Roslyn's
+/// SerializeLocalSlots. Returns the empty array when there are no slots (no CDI row
+/// should be emitted then).
+val serializeLocalSlots: info: EncMethodDebugInformation -> byte[]
+
+/// Deserializes an EnC Local Slot Map blob, byte-for-byte as Roslyn's UncompressSlotMap.
+/// An empty (or null) blob yields no slots.
+val deserializeLocalSlots: blob: byte[] -> EncLocalSlotInfo list
+
+/// Serializes the EnC Lambda and Closure Map blob for 'info', byte-for-byte as Roslyn's
+/// SerializeLambdaMap. Returns the empty array when there are no lambdas and no closures
+/// (Roslyn's MetadataWriter skips the CDI row in that case; note the method ordinal is
+/// then not persisted and decodes back as UndefinedMethodOrdinal).
+val serializeLambdaMap: info: EncMethodDebugInformation -> byte[]
+
+/// Deserializes an EnC Lambda and Closure Map blob, byte-for-byte as Roslyn's
+/// UncompressLambdaMap. An empty (or null) blob yields (UndefinedMethodOrdinal, [], []).
+val deserializeLambdaMap: blob: byte[] -> int * EncClosureInfo list * EncLambdaInfo list
+
+/// Serializes the EnC State Machine State Map blob for 'info', byte-for-byte as
+/// Roslyn's SerializeStateMachineStates: entries are sorted by syntax offset (stably,
+/// preserving relative order of equal offsets, which encodes the per-offset relative
+/// ordinal). Returns the empty array when there are no states (no CDI row then).
+val serializeStateMachineStates: info: EncMethodDebugInformation -> byte[]
+
+/// Deserializes an EnC State Machine State Map blob, byte-for-byte as Roslyn's
+/// UncompressStateMachineStates (including the ordered-by-offset and <= 256-per-offset
+/// validations). An empty (or null) blob yields no states.
+val deserializeStateMachineStates: blob: byte[] -> EncStateMachineStateInfo list
+
+/// Deserializes EnC method debug information from the three blobs (any of which may be
+/// null or empty). Mirrors Roslyn's EditAndContinueMethodDebugInformation.Create.
+val deserialize:
+ slotMapBlob: byte[] -> lambdaMapBlob: byte[] -> stateMachineStateMapBlob: byte[] -> EncMethodDebugInformation
+
+/// Decodes every method-level EnC CustomDebugInformation row of a portable PDB image into
+/// per-method EnC debug information, keyed by MethodDef token (0x06xxxxxx). The CDI parent
+/// of the EnC rows is always a MethodDef handle, so token keying is unambiguous.
+/// Fail safe: a null/empty or non-PDB image yields the empty map, and a method whose
+/// blobs do not decode is omitted rather than guessed.
+val readEncMethodDebugInfoFromPortablePdb: pdbBytes: byte[] -> Map
diff --git a/src/Compiler/AbstractIL/il.fs b/src/Compiler/AbstractIL/il.fs
index c3023ed9579..e2002731aa8 100644
--- a/src/Compiler/AbstractIL/il.fs
+++ b/src/Compiler/AbstractIL/il.fs
@@ -1258,6 +1258,7 @@ type WellKnownILAttributes =
| RequiredMemberAttribute = (1u <<< 22)
| NullableContextAttribute = (1u <<< 23)
| AttributeUsageAttribute = (1u <<< 24)
+ | NotNullIfNotNullAttribute = (1u <<< 25)
| NotComputed = (1u <<< 31)
type internal ILAttributesStoredRepr =
diff --git a/src/Compiler/AbstractIL/il.fsi b/src/Compiler/AbstractIL/il.fsi
index aef29b61d9b..050921650c3 100644
--- a/src/Compiler/AbstractIL/il.fsi
+++ b/src/Compiler/AbstractIL/il.fsi
@@ -912,6 +912,7 @@ type WellKnownILAttributes =
| RequiredMemberAttribute = (1u <<< 22)
| NullableContextAttribute = (1u <<< 23)
| AttributeUsageAttribute = (1u <<< 24)
+ | NotNullIfNotNullAttribute = (1u <<< 25)
| NotComputed = (1u <<< 31)
/// Represents the efficiency-oriented storage of ILAttributes in another item.
diff --git a/src/Compiler/AbstractIL/ilwrite.fs b/src/Compiler/AbstractIL/ilwrite.fs
index 13feeab294a..bf6277bf485 100644
--- a/src/Compiler/AbstractIL/ilwrite.fs
+++ b/src/Compiler/AbstractIL/ilwrite.fs
@@ -2699,8 +2699,11 @@ let GenMethodDefAsRow cenv env midx (mdef: ILMethodDef) =
cenv.AddCode code
addr
| MethodBody.Abstract
- | MethodBody.PInvoke _ ->
+ | MethodBody.PInvoke _
+ | MethodBody.NotAvailable ->
// Now record the PDB record for this method - we write this out later.
+ // Metadata-only methods still participate in name ambiguity checks and occupy
+ // MethodDebugInformation rows even though they have no sequence points.
if cenv.generatePdb then
cenv.pdbinfo.Add
{ MethToken = getUncodedToken TableNames.Method midx
@@ -2713,7 +2716,7 @@ let GenMethodDefAsRow cenv env midx (mdef: ILMethodDef) =
0x0000
| MethodBody.Native ->
failwith "cannot write body of native method - Abstract IL cannot roundtrip mixed native/managed binaries"
- | _ -> 0x0000)
+ )
UnsharedRow
[| ULong codeAddr
@@ -3859,7 +3862,10 @@ type options =
referenceAssemblyOnly: bool
referenceAssemblyAttribOpt: ILAttribute option
referenceAssemblySignatureHash : int option
- pathMap: PathMap }
+ pathMap: PathMap
+ /// Per-method EnC CustomDebugInformation rows for the portable PDB writer, keyed by
+ /// IL method name. Empty for ordinary compiles, so flag-off output stays byte-identical.
+ methodCustomDebugInfoRows: Map }
let writeBinaryAux (stream: Stream, options: options, modul, normalizeAssemblyRefs) =
@@ -4022,7 +4028,7 @@ let writeBinaryAux (stream: Stream, options: options, modul, normalizeAssemblyRe
match options.pdbfile, options.portablePDB with
| Some _, true ->
let pdbInfo =
- generatePortablePdb options.embedAllSource options.embedSourceList options.sourceLink options.checksumAlgorithm pdbData options.pathMap
+ generatePortablePdb options.embedAllSource options.embedSourceList options.sourceLink options.checksumAlgorithm pdbData options.pathMap options.methodCustomDebugInfoRows
if options.embeddedPDB then
let uncompressedLength, contentId, stream, algorithmName, checkSum = pdbInfo
diff --git a/src/Compiler/AbstractIL/ilwrite.fsi b/src/Compiler/AbstractIL/ilwrite.fsi
index d074f0bc584..08321664c2f 100644
--- a/src/Compiler/AbstractIL/ilwrite.fsi
+++ b/src/Compiler/AbstractIL/ilwrite.fsi
@@ -9,24 +9,29 @@ open FSharp.Compiler.AbstractIL.ILPdbWriter
open FSharp.Compiler.AbstractIL.StrongNameSign
type options =
- { ilg: ILGlobals
- outfile: string
- pdbfile: string option
- portablePDB: bool
- embeddedPDB: bool
- embedAllSource: bool
- embedSourceList: string list
- allGivenSources: ILSourceDocument list
- sourceLink: string
- checksumAlgorithm: HashAlgorithm
- signer: ILStrongNameSigner option
- emitTailcalls: bool
- deterministic: bool
- dumpDebugInfo: bool
- referenceAssemblyOnly: bool
- referenceAssemblyAttribOpt: ILAttribute option
- referenceAssemblySignatureHash: int option
- pathMap: PathMap }
+ {
+ ilg: ILGlobals
+ outfile: string
+ pdbfile: string option
+ portablePDB: bool
+ embeddedPDB: bool
+ embedAllSource: bool
+ embedSourceList: string list
+ allGivenSources: ILSourceDocument list
+ sourceLink: string
+ checksumAlgorithm: HashAlgorithm
+ signer: ILStrongNameSigner option
+ emitTailcalls: bool
+ deterministic: bool
+ dumpDebugInfo: bool
+ referenceAssemblyOnly: bool
+ referenceAssemblyAttribOpt: ILAttribute option
+ referenceAssemblySignatureHash: int option
+ pathMap: PathMap
+ /// Per-method EnC CustomDebugInformation rows for the portable PDB writer, keyed by
+ /// IL method name. Empty for ordinary compiles, so flag-off output stays byte-identical.
+ methodCustomDebugInfoRows: Map
+ }
/// Write a binary to the file system.
val WriteILBinaryFile: options: options * inputModule: ILModuleDef * (ILAssemblyRef -> ILAssemblyRef) -> unit
diff --git a/src/Compiler/AbstractIL/ilwritepdb.fs b/src/Compiler/AbstractIL/ilwritepdb.fs
index 86a19d50c6c..70f88b471d7 100644
--- a/src/Compiler/AbstractIL/ilwritepdb.fs
+++ b/src/Compiler/AbstractIL/ilwritepdb.fs
@@ -118,6 +118,10 @@ type PdbMethodData =
DebugPoints: PdbDebugPoint array
}
+/// A pre-serialized CustomDebugInformation row (kind GUID + blob) to attach to a method
+/// definition row in the portable PDB.
+type PdbMethodCustomDebugInfo = { KindGuid: Guid; Blob: byte[] }
+
module SequencePoint =
let orderBySource sp1 sp2 =
let c1 = compare sp1.Document sp2.Document
@@ -337,7 +341,15 @@ let scopeSorter (scope1: PdbMethodScope) (scope2: PdbMethodScope) =
0
type PortablePdbGenerator
- (embedAllSource: bool, embedSourceList: string list, sourceLink: string, checksumAlgorithm, info: PdbData, pathMap: PathMap) =
+ (
+ embedAllSource: bool,
+ embedSourceList: string list,
+ sourceLink: string,
+ checksumAlgorithm,
+ info: PdbData,
+ pathMap: PathMap,
+ methodCustomDebugInfoRows: Map
+ ) =
// Deterministic: build the Document table in a stable order by mapped file path,
// but preserve the original-document-index -> handle mapping by filename.
@@ -488,6 +500,27 @@ type PortablePdbGenerator
let moduleImportScopeHandle = MetadataTokens.ImportScopeHandle(1)
let importScopesTable = Dictionary()
+ // Per-method CustomDebugInformation rows keyed by IL method name. Names that match
+ // more than one method row (overloads, same name on different types) fail closed and
+ // attach nothing, so a row can never land on the wrong method.
+ let methodCustomDebugInfoByName =
+ if Map.isEmpty methodCustomDebugInfoRows then
+ methodCustomDebugInfoRows
+ else
+ let nameCounts = Dictionary()
+
+ for minfo in info.Methods do
+ nameCounts[minfo.MethName] <-
+ match nameCounts.TryGetValue minfo.MethName with
+ | true, count -> count + 1
+ | _ -> 1
+
+ methodCustomDebugInfoRows
+ |> Map.filter (fun methName _ ->
+ match nameCounts.TryGetValue methName with
+ | true, 1 -> true
+ | _ -> false)
+
let serializeImport (writer: BlobBuilder) (import: PdbImport) =
match import with
// We don't yet emit these kinds of imports
@@ -777,6 +810,23 @@ type PortablePdbGenerator
metadata.AddMethodDebugInformation(docHandle, sequencePointBlob) |> ignore
+ // MetadataBuilder sorts the CustomDebugInformation table by parent at serialize
+ // time, so adding rows in method order here is safe.
+ match Map.tryFind minfo.MethName methodCustomDebugInfoByName with
+ | Some cdiRows ->
+ // MethToken is the uncoded token (0x06 <<< 24 ||| rid); the handle needs the rid.
+ let methodHandle =
+ MetadataTokens.MethodDefinitionHandle(minfo.MethToken &&& 0x00FFFFFF)
+
+ for cdiRow in cdiRows do
+ metadata.AddCustomDebugInformation(
+ MethodDefinitionHandle.op_Implicit methodHandle,
+ metadata.GetOrAddGuid cdiRow.KindGuid,
+ metadata.GetOrAddBlob cdiRow.Blob
+ )
+ |> ignore
+ | None -> ()
+
match minfo.RootScope with
| None -> ()
| Some scope -> writeMethodScopes minfo.MethToken scope
@@ -831,9 +881,10 @@ let generatePortablePdb
checksumAlgorithm
(info: PdbData)
(pathMap: PathMap)
+ (methodCustomDebugInfoRows: Map)
=
let generator =
- PortablePdbGenerator(embedAllSource, embedSourceList, sourceLink, checksumAlgorithm, info, pathMap)
+ PortablePdbGenerator(embedAllSource, embedSourceList, sourceLink, checksumAlgorithm, info, pathMap, methodCustomDebugInfoRows)
generator.Emit()
diff --git a/src/Compiler/AbstractIL/ilwritepdb.fsi b/src/Compiler/AbstractIL/ilwritepdb.fsi
index 5987cc165e3..09d380e44cc 100644
--- a/src/Compiler/AbstractIL/ilwritepdb.fsi
+++ b/src/Compiler/AbstractIL/ilwritepdb.fsi
@@ -67,6 +67,12 @@ type PdbMethodData =
DebugRange: (PdbSourceLoc * PdbSourceLoc) option
DebugPoints: PdbDebugPoint[] }
+/// A pre-serialized CustomDebugInformation row to attach to a method definition row in
+/// the portable PDB (kind GUID + blob). Supplied by the compiler as a side channel keyed
+/// by IL method name. The writer attaches the rows only when the name identifies exactly
+/// one method row (fail closed on ambiguity).
+type PdbMethodCustomDebugInfo = { KindGuid: System.Guid; Blob: byte[] }
+
[]
type PdbData =
{
@@ -109,6 +115,7 @@ val generatePortablePdb:
checksumAlgorithm: HashAlgorithm ->
info: PdbData ->
pathMap: PathMap ->
+ methodCustomDebugInfoRows: Map ->
int64 * BlobContentId * MemoryStream * string * byte[]
val compressPortablePdbStream: stream: MemoryStream -> MemoryStream
diff --git a/src/Compiler/Checking/AugmentWithHashCompare.fs b/src/Compiler/Checking/AugmentWithHashCompare.fs
index c5ae2d1459f..0ae09df3996 100644
--- a/src/Compiler/Checking/AugmentWithHashCompare.fs
+++ b/src/Compiler/Checking/AugmentWithHashCompare.fs
@@ -81,6 +81,9 @@ let mkGetHashCodeSlotSig (g: TcGlobals) =
let mkEqualsSlotSig (g: TcGlobals) =
TSlotSig("Equals", g.obj_ty_noNulls, [], [], [ [ TSlotParam(Some("obj"), g.obj_ty_withNulls, false, false, false, []) ] ], Some g.bool_ty)
+let mkToStringSlotSig (g: TcGlobals) =
+ TSlotSig("ToString", g.obj_ty_noNulls, [], [], [ [] ], Some g.string_ty)
+
//-------------------------------------------------------------------------
// Helpers associated with code-generation of comparison/hash augmentations
//-------------------------------------------------------------------------
@@ -112,6 +115,9 @@ let mkEqualsWithComparerTyExact g ty =
let mkHashTy g ty =
mkFunTy g (mkThisTy g ty) (mkFunTy g g.unit_ty g.int_ty)
+let mkToStringTy (g: TcGlobals, ty: TType) =
+ mkFunTy g (mkThisTy g ty) (mkFunTy g g.unit_ty g.string_ty)
+
let mkHashWithComparerTy g ty =
mkFunTy g (mkThisTy g ty) (mkFunTy g g.IEqualityComparer_ty g.int_ty)
@@ -1697,3 +1703,145 @@ let MakeBindingsForUnionAugmentation g (tycon: Tycon) (vals: ValRef list) =
let isdata = mkUnionCaseTest g (thise, ucr, tinst, m)
let expr = mkLambdas g m tps [ thisv; unitv ] (isdata, g.bool_ty)
mkCompGenBind v.Deref expr)
+
+//-------------------------------------------------------------------------
+// Build reflection-free ToString functions for union and record types.
+//
+// Under --reflectionfree the reflective 'sprintf "%+A"' ToString is unavailable, so we build a structural
+// one here (during type augmentation, so the 'string' operator calls flow through the optimizer and get
+// specialised - e.g. an int field renders via a direct, allocation-free ToString rather than a boxed call).
+//-------------------------------------------------------------------------
+
+// Guard deep recursion with a catchable exception, as C# records' PrintMembers do, when the runtime provides
+// it. A type whose fields are all primitive cannot nest, so it skips the guard.
+let mkToStringRecursionGuard (g: TcGlobals, m: Text.range, fieldTys: TType list, body: Expr) =
+ let isPrimitive (ty: TType) =
+ isIntegerTy g ty
+ || isFpTy g ty
+ || isDecimalTy g ty
+ || isStringTy g ty
+ || typeEquiv g g.char_ty ty
+ || isBoolTy g ty
+ || isUnitTy g ty
+ || isEnumTy g ty
+
+ if fieldTys |> List.forall isPrimitive then
+ body
+ else
+ match g.TryFindSysILTypeRef "System.Runtime.CompilerServices.RuntimeHelpers" with
+ | Some tref ->
+ let mspec =
+ mkILNonGenericStaticMethSpecInTy (mkILNonGenericBoxedTy tref, "EnsureSufficientExecutionStack", [], ILType.Void)
+
+ mkSequential m (mkAsmExpr ([ mkNormalCall mspec ], [], [], [], m)) body
+ | None -> body
+
+// Render one field value as a string the way option/list do (LanguagePrimitives.anyToStringShowingNull):
+// a null reference renders as "null", everything else via the 'string' operator. A value-type field can
+// never be null, so it skips the box+null-guard and renders directly.
+let mkFieldToString (g: TcGlobals, m: Text.range, fe: Expr) =
+ let fieldTy = tyOfExpr g fe
+
+ if isStructTy g fieldTy then
+ mkCallStringOperator g m fieldTy fe
+ else
+ let v, ve = mkCompGenLocal m "field" fieldTy
+ mkCompGenLet m v fe (mkNonNullCond g m g.string_ty (mkCallBox g m fieldTy ve) (mkCallStringOperator g m fieldTy ve) (mkString g m "null"))
+
+// A record's ToString as a single line "{ F1 = v1; F2 = v2 }" (no line breaks, unlike "%+A").
+// openBrace/closeBrace are "{ "/" }" for records and "{| "/" |}" for anonymous records.
+let mkRecdToString (g: TcGlobals, tcref: TyconRef, tycon: Tycon, openBrace: string, closeBrace: string) =
+ let m = tycon.Range
+ let tinst, ty = mkMinimalTy g tcref
+ let thisv, thise = mkThisVar g m ty
+
+ let fieldParts =
+ tcref.AllInstanceFieldsAsList
+ |> List.mapi (fun i fspec ->
+ let fref = tcref.MakeNestedRecdFieldRef fspec
+ let value = mkFieldToString (g, m, mkRecdFieldGetViaExprAddr (thise, fref, tinst, m))
+ let nameEq = mkString g m (fspec.DisplayNameCore + " = ")
+ if i = 0 then [ nameEq; value ] else [ mkString g m "; "; nameEq; value ])
+ |> List.concat
+
+ let close =
+ if List.isEmpty fieldParts then
+ // Avoid a double space in an empty record.
+ closeBrace.TrimStart()
+ else closeBrace
+ let parts = mkString g m openBrace :: fieldParts @ [ mkString g m close ]
+ let fieldTys = tcref.AllInstanceFieldsAsList |> List.map (fun fspec -> fspec.FormalType)
+ thisv, mkToStringRecursionGuard (g, m, fieldTys, mkStringConcat (g, m, parts))
+
+// A union's ToString as a match over the cases building "CaseName(f0, f1, ...)" (or just "CaseName" for a
+// nullary case).
+let mkUnionToString (g: TcGlobals, tcref: TyconRef, tycon: Tycon) =
+ let m = tycon.Range
+ let tinst, ty = mkMinimalTy g tcref
+ let thisv, thise = mkThisVar g m ty
+ let mbuilder = MatchBuilder(DebugPointAtBinding.NoneAtInvisible, m)
+
+ let mkResult (ucase: UnionCase) =
+ let cref = tcref.MakeNestedUnionCaseRef ucase
+ let rfields = ucase.RecdFields
+
+ if isNil rfields then
+ mkString g m ucase.DisplayNameCore
+ else
+ // provene is an expression proven to be of this case (the value itself for struct unions,
+ // otherwise a 'UnionCaseProof'), from which fields can be read.
+ let mkBody (provene: Expr) =
+ let fieldStrs =
+ rfields
+ |> List.mapi (fun j _ -> mkFieldToString (g, m, mkUnionCaseFieldGetProvenViaExprAddr (provene, cref, tinst, j, m)))
+
+ let sep = mkString g m ", "
+
+ let fieldsWithSeps =
+ fieldStrs |> List.mapi (fun i fe -> if i = 0 then [ fe ] else [ sep; fe ]) |> List.concat
+
+ let parts = mkString g m (ucase.DisplayNameCore + "(") :: fieldsWithSeps @ [ mkString g m ")" ]
+ mkStringConcat (g, m, parts)
+
+ if cref.Tycon.IsStructOrEnumTycon then
+ mkBody thise
+ else
+ let ucv, ucve = mkCompGenLocal m "thisCast" (mkProvenUnionCaseTy cref tinst)
+ mkCompGenLet m ucv (mkUnionCaseProof (thise, cref, tinst, m)) (mkBody ucve)
+
+ let cases =
+ tcref.UnionCasesAsList
+ |> List.map (fun ucase ->
+ let cref = tcref.MakeNestedUnionCaseRef ucase
+ mkCase (DecisionTreeTest.UnionCase(cref, tinst), mbuilder.AddResultTarget(mkResult ucase)))
+
+ let dtree = TDSwitch(thise, cases, None, m)
+
+ let fieldTys =
+ tcref.UnionCasesAsList |> List.collect (fun uc -> uc.RecdFields) |> List.map (fun rf -> rf.FormalType)
+
+ thisv, mkToStringRecursionGuard (g, m, fieldTys, mbuilder.Close(dtree, m, g.string_ty))
+
+let TyconIsCandidateForAugmentationWithToString (g: TcGlobals, tycon: Tycon) =
+ g.useReflectionFreeCodeGen && (tycon.IsUnionTycon || tycon.IsRecordTycon)
+
+let MakeValsForToStringAugmentation (g: TcGlobals, tcref: TyconRef) =
+ let _, ty = mkMinimalTy g tcref
+ let vis = tcref.Accessibility
+ let tps = tcref.Typars
+ mkValSpec g tcref ty vis (Some(mkToStringSlotSig g)) "ToString" (tps +-> (mkToStringTy (g, ty))) unitArg false
+
+let MakeBindingsForToStringAugmentation (g: TcGlobals, tycon: Tycon, toStringVal: Val) =
+ let tcref = mkLocalTyconRef tycon
+ let m = tycon.Range
+ let tps = tycon.Typars
+
+ let thisv, body =
+ if tycon.IsUnionTycon then
+ mkUnionToString (g, tcref, tycon)
+ else
+ mkRecdToString (g, tcref, tycon, "{ ", " }")
+
+ let unitv, _ = mkCompGenLocal m "unitArg" g.unit_ty
+ let expr = mkLambdas g m tps [ thisv; unitv ] (body, g.string_ty)
+ [ mkCompGenBind toStringVal expr ]
diff --git a/src/Compiler/Checking/AugmentWithHashCompare.fsi b/src/Compiler/Checking/AugmentWithHashCompare.fsi
index b57e25f32cc..424026f1330 100644
--- a/src/Compiler/Checking/AugmentWithHashCompare.fsi
+++ b/src/Compiler/Checking/AugmentWithHashCompare.fsi
@@ -51,3 +51,15 @@ val TypeDefinitelyHasEquality: TcGlobals -> TType -> bool
val MakeValsForUnionAugmentation: TcGlobals -> TyconRef -> Val list
val MakeBindingsForUnionAugmentation: TcGlobals -> Tycon -> ValRef list -> Binding list
+
+/// Build a record's single-line reflection-free ToString body, recursion guard included; returns the 'this' value and the body expression.
+val mkRecdToString: g: TcGlobals * tcref: TyconRef * tycon: Tycon * openBrace: string * closeBrace: string -> Val * Expr
+
+/// Whether a reflection-free structural ToString should be generated for this type.
+val TyconIsCandidateForAugmentationWithToString: g: TcGlobals * tycon: Tycon -> bool
+
+/// Make the ToString override slot for a reflection-free record or union.
+val MakeValsForToStringAugmentation: g: TcGlobals * tcref: TyconRef -> Val
+
+/// Build the body binding for a reflection-free record or union ToString override.
+val MakeBindingsForToStringAugmentation: g: TcGlobals * tycon: Tycon * toStringVal: Val -> Binding list
diff --git a/src/Compiler/Checking/CheckDeclarations.fs b/src/Compiler/Checking/CheckDeclarations.fs
index b5055ce2dd9..8b8acecaba0 100644
--- a/src/Compiler/Checking/CheckDeclarations.fs
+++ b/src/Compiler/Checking/CheckDeclarations.fs
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
+// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
module internal FSharp.Compiler.CheckDeclarations
@@ -431,14 +431,17 @@ module TcRecdUnionAndEnumDeclarations =
let vis = CombineReprAccess parent vis
Construct.NewRecdField isStatic konst id nameGenerated tyR isMutable vol attrsForProperty attrsForField xmldoc vis false
- let TcFieldDecl (cenv: cenv) env parent isIncrClass tpenv (isStatic, synAttrs, id: Ident, nameGenerated, ty, isMutable, xmldoc, vis) =
+ let TcFieldDecl (cenv: cenv) env parent isIncrClass tpenv addFixup (isStatic, synAttrs, id: Ident, nameGenerated, ty, isMutable, xmldoc, vis) =
let g = cenv.g
let m = id.idRange
- let attrs, _ = TcAttributesWithPossibleTargets TcCanFail.ReportAllErrors cenv env AttributeTargets.FieldDecl synAttrs
+ // Attribute types from the same recursive group may not resolve yet; the fixup re-resolves later.
+ let attrs, hasUnresolvedAttrs = TcAttributesWithPossibleTargets TcCanFail.IgnoreAllErrors cenv env AttributeTargets.FieldDecl synAttrs
- let attrsForProperty, attrsForField = attrs |> List.partition (fun (attrTargets, _) -> (attrTargets &&& AttributeTargets.Property) <> enum 0)
- let attrsForProperty = (List.map snd attrsForProperty)
- let attrsForField = (List.map snd attrsForField)
+ let splitAttrs (attrsWithTargets: (AttributeTargets * Attrib) list) =
+ let propAttribs, fieldAttribs = attrsWithTargets |> List.partition (fun (attrTargets, _) -> (attrTargets &&& AttributeTargets.Property) <> enum 0)
+ List.map snd propAttribs, List.map snd fieldAttribs
+
+ let attrsForProperty, attrsForField = splitAttrs attrs
let tyR, _ = TcTypeAndRecover cenv NoNewTypars CheckCxs ItemOccurrence.UseInType WarnOnIWSAM.Yes env tpenv ty
let fieldFlags = computeValWellKnownFlags g attrsForField
let zeroInit = hasFlag fieldFlags (WellKnownValAttributes.DefaultValueAttribute_True ||| WellKnownValAttributes.DefaultValueAttribute_False)
@@ -457,22 +460,37 @@ module TcRecdUnionAndEnumDeclarations =
if isStatic && (not zeroInit || not isMutable || not isPrivate) then errorR(Error(FSComp.SR.tcStaticValFieldsMustBeMutableAndPrivate(), m))
let konst = if zeroInit then Some Const.Zero else None
let rfspec = MakeRecdFieldSpec g env parent (isStatic, konst, tyR, attrsForProperty, attrsForField, id, nameGenerated, isMutable, isVolatile, xmldoc, vis, m)
- match parent with
- | Parent tcref when useGenuineField tcref.Deref rfspec ->
- // Recheck the attributes for errors if the definition only generates a field
- TcAttributesWithPossibleTargets TcCanFail.ReportAllErrors cenv env AttributeTargets.FieldDeclRestricted synAttrs |> ignore
- | _ -> ()
+ let isGenuineField = match parent with Parent tcref -> useGenuineField tcref.Deref rfspec | _ -> false
+
+ // Recheck the attributes for errors if the definition only generates a field. When the attribute type
+ // is from the same recursive group its constructor is not yet established, so defer to the fixup below.
+ let recheckGenuineField () =
+ if isGenuineField then
+ TcAttributesWithPossibleTargets TcCanFail.ReportAllErrors cenv env AttributeTargets.FieldDeclRestricted synAttrs |> ignore
+ if not hasUnresolvedAttrs then recheckGenuineField ()
+
+ let fixupAttrs () =
+ let finalAttrs =
+ if hasUnresolvedAttrs then
+ let reresolved = TcAttributesWithPossibleTargets TcCanFail.ReportAllErrors cenv env AttributeTargets.FieldDecl synAttrs |> fst
+ recheckGenuineField ()
+ reresolved
+ else attrs
+ let propAttribs', fieldAttribs' = splitAttrs finalAttrs
+ rfspec.rfield_pattribs <- propAttribs'
+ rfspec.rfield_fattribs <- fieldAttribs'
+ addFixup fixupAttrs
rfspec
- let TcAnonFieldDecl cenv env parent tpenv nm (SynField(Attributes attribs, isStatic, idOpt, ty, isMutable, xmldoc, vis, m, _)) =
+ let TcAnonFieldDecl cenv env parent tpenv addFixup nm (SynField(Attributes attribs, isStatic, idOpt, ty, isMutable, xmldoc, vis, m, _)) =
let mName = m.MakeSynthetic()
let id = match idOpt with None -> mkSynId mName nm | Some id -> id
let checkXmlDocs = cenv.diagnosticOptions.CheckXmlDocs
let xmlDoc = xmldoc.ToXmlDoc(checkXmlDocs, Some [])
- TcFieldDecl cenv env parent false tpenv (isStatic, attribs, id, idOpt.IsNone, ty, isMutable, xmlDoc, vis)
+ TcFieldDecl cenv env parent false tpenv addFixup (isStatic, attribs, id, idOpt.IsNone, ty, isMutable, xmlDoc, vis)
- let TcNamedFieldDecl cenv env parent isIncrClass tpenv (SynField(Attributes attribs, isStatic, id, ty, isMutable, xmldoc, vis, m, _)) =
+ let TcNamedFieldDecl cenv env parent isIncrClass tpenv addFixup (SynField(Attributes attribs, isStatic, id, ty, isMutable, xmldoc, vis, m, _)) =
match id with
| None ->
errorR (Error(FSComp.SR.tcFieldRequiresName(), m))
@@ -480,10 +498,10 @@ module TcRecdUnionAndEnumDeclarations =
| Some id ->
let checkXmlDocs = cenv.diagnosticOptions.CheckXmlDocs
let xmlDoc = xmldoc.ToXmlDoc(checkXmlDocs, Some [])
- Some(TcFieldDecl cenv env parent isIncrClass tpenv (isStatic, attribs, id, false, ty, isMutable, xmlDoc, vis))
+ Some(TcFieldDecl cenv env parent isIncrClass tpenv addFixup (isStatic, attribs, id, false, ty, isMutable, xmlDoc, vis))
- let TcNamedFieldDecls cenv env parent isIncrClass tpenv fields =
- fields |> List.choose (TcNamedFieldDecl cenv env parent isIncrClass tpenv)
+ let TcNamedFieldDecls cenv env parent isIncrClass tpenv addFixup fields =
+ fields |> List.choose (TcNamedFieldDecl cenv env parent isIncrClass tpenv addFixup)
//-------------------------------------------------------------------------
// Bind other elements of type definitions (constructors etc.)
@@ -528,13 +546,15 @@ module TcRecdUnionAndEnumDeclarations =
| _ ->
seen.Add(f.LogicalName, sf))
- let TcUnionCaseDecl (cenv: cenv) env parent thisTy thisTyInst tpenv hasRQAAttribute (SynUnionCase(Attributes synAttrs, SynIdent(id, _), args, xmldoc, vis, m, _)) =
+ let TcUnionCaseDecl (cenv: cenv) env parent thisTy thisTyInst tpenv hasRQAAttribute addFixup (SynUnionCase(Attributes synAttrs, SynIdent(id, _), args, xmldoc, vis, m, _)) =
let g = cenv.g
let vis, _ = ComputeAccessAndCompPath g env None m vis None parent
let vis = CombineReprAccess parent vis
CheckUnionCaseName cenv id hasRQAAttribute
+ // Field fixups run after the union-case attributes below, preserving the non-deferred order.
+ let fieldFixups = ResizeArray()
let rfields, recordTy =
match args with
| SynUnionCaseKind.Fields flds ->
@@ -546,9 +566,9 @@ module TcRecdUnionAndEnumDeclarations =
| Some fieldId, Parent tcref ->
let item = Item.UnionCaseField (UnionCaseInfo (thisTyInst, UnionCaseRef (tcref, id.idText)), i)
CallNameResolutionSink cenv.tcSink (fieldId.idRange, env.NameEnv, item, emptyTyparInst, ItemOccurrence.Binding, env.AccessRights)
- TcNamedFieldDecl cenv env parent false tpenv fld
+ TcNamedFieldDecl cenv env parent false tpenv fieldFixups.Add fld
| _ ->
- Some(TcAnonFieldDecl cenv env parent tpenv (mkUnionCaseFieldName nFields i) fld)
+ Some(TcAnonFieldDecl cenv env parent tpenv fieldFixups.Add (mkUnionCaseFieldName nFields i) fld)
)
|> List.choose (fun x -> x)
@@ -582,42 +602,50 @@ module TcRecdUnionAndEnumDeclarations =
let checkXmlDocs = cenv.diagnosticOptions.CheckXmlDocs
let xmlDoc = xmldoc.ToXmlDoc(checkXmlDocs, Some names)
- let attrs = TcAttributes cenv env AttributeTargets.UnionCaseDecl synAttrs
- (*
- The attributes of a union case decl get attached to the generated "static factory" method.
- Enforce union-cases AttributeTargets:
- - AttributeTargets.Method
- type SomeUnion =
- | Case1 of int // Compiles down to a static method
- - AttributeTargets.Property
- type SomeUnion =
- | Case1 // Compiles down to a static property
- *)
- if g.langVersion.SupportsFeature(LanguageFeature.EnforceAttributeTargets) then
- let attrTargets =
- attrs
- |> List.collect (fun attr ->
- attr.TyconRef.Attribs
- |> List.choose (fun attr ->
- match attr with
- | Attrib(unnamedArgs = [ AttribInt32Arg validOn ]) -> Some validOn
- | _ -> None))
-
- attrTargets
- |> List.iter (fun target ->
- // If the union case has fields, and the target is not AttributeTargets.Method || AttributeTargets.All. Then we raise a separate opt-in warning
- let hasNotMethodTarget = (enum target &&& AttributeTargets.Method) = enum 0
- if hasNotMethodTarget then
- warning(Error(FSComp.SR.tcAttributeIsNotValidForUnionCaseWithFields(), id.idRange)))
-
- Construct.NewUnionCase id rfields recordTy attrs xmlDoc vis
-
- let TcUnionCaseDecls (cenv: cenv) env (parent: ParentRef) (thisTy: TType) (thisTyInst: TypeInst) hasRQAAttribute tpenv unionCases =
+ let attrs, getFinalAttrs = TcAttributesCanFail cenv env AttributeTargets.UnionCaseDecl synAttrs
+ let unionCase = Construct.NewUnionCase id rfields recordTy attrs xmlDoc vis
+
+ // Attribute types from the same recursive group resolve only once the group is established.
+ addFixup (fun () ->
+ let attrs = getFinalAttrs ()
+ unionCase.Attribs <- attrs
+ (*
+ The attributes of a union case decl get attached to the generated "static factory" method.
+ Enforce union-cases AttributeTargets:
+ - AttributeTargets.Method
+ type SomeUnion =
+ | Case1 of int // Compiles down to a static method
+ - AttributeTargets.Property
+ type SomeUnion =
+ | Case1 // Compiles down to a static property
+ *)
+ if g.langVersion.SupportsFeature(LanguageFeature.EnforceAttributeTargets) then
+ let attrTargets =
+ attrs
+ |> List.collect (fun attr ->
+ attr.TyconRef.Attribs
+ |> List.choose (fun attr ->
+ match attr with
+ | Attrib(unnamedArgs = [ AttribInt32Arg validOn ]) -> Some validOn
+ | _ -> None))
+
+ attrTargets
+ |> List.iter (fun target ->
+ // If the union case has fields, and the target is not AttributeTargets.Method || AttributeTargets.All. Then we raise a separate opt-in warning
+ let hasNotMethodTarget = (enum target &&& AttributeTargets.Method) = enum 0
+ if hasNotMethodTarget then
+ warning(Error(FSComp.SR.tcAttributeIsNotValidForUnionCaseWithFields(), id.idRange)))
+
+ for f in fieldFixups do f())
+
+ unionCase
+
+ let TcUnionCaseDecls (cenv: cenv) env (parent: ParentRef) (thisTy: TType) (thisTyInst: TypeInst) hasRQAAttribute tpenv addFixup unionCases =
let unionCasesR =
unionCases
|> List.filter (fun (SynUnionCase(_, SynIdent(id, _), _, _, _, _, _)) -> id.idText <> "")
- |> List.map (TcUnionCaseDecl cenv env parent thisTy thisTyInst tpenv hasRQAAttribute)
- unionCasesR |> CheckDuplicates (fun uc -> uc.Id) "union case"
+ |> List.map (TcUnionCaseDecl cenv env parent thisTy thisTyInst tpenv hasRQAAttribute addFixup)
+ unionCasesR |> CheckDuplicates (fun uc -> uc.Id) "union case"
let MakeEnumCaseSpec g cenv env parent attrs thisTy caseRange (caseIdent: Ident) (xmldoc: PreXmlDoc) value =
let vis, _ = ComputeAccessAndCompPath g env None caseRange None None parent
@@ -916,6 +944,18 @@ module AddAugmentationDeclarations =
else []
else []
+ // Under --reflectionfree the structural ToString is generated here (rather than in IlxGen) so the 'string'
+ // operator calls in its body flow through the optimizer and get specialised. Like the Equals override, this
+ // runs late so tycon.HasMember gives correct results for a user-written ToString.
+ let AddReflectionFreeToStringBindings (cenv: cenv, env: TcEnv, tycon: Tycon) =
+ let g = cenv.g
+ if AugmentTypeDefinitions.TyconIsCandidateForAugmentationWithToString(g, tycon) && not (tycon.HasMember g "ToString" []) then
+ let tcref = mkLocalTyconRef tycon
+ let toStringVal = AugmentTypeDefinitions.MakeValsForToStringAugmentation(g, tcref)
+ PublishValueDefn cenv env ModuleOrMemberBinding toStringVal
+ AugmentTypeDefinitions.MakeBindingsForToStringAugmentation(g, tycon, toStringVal)
+ else []
+
let ShouldAugmentUnion (g: TcGlobals) (tycon: Tycon) =
g.langVersion.SupportsFeature LanguageFeature.UnionIsPropertiesVisible &&
HasDefaultAugmentationAttribute g (mkLocalTyconRef tycon) &&
@@ -2448,7 +2488,7 @@ module TcExceptionDeclarations =
CallNameResolutionSink cenv.tcSink (fieldId.idRange, env.NameEnv, item, emptyTyparInst, ItemOccurrence.Binding, env.AccessRights)
| _ -> ()
- TcRecdUnionAndEnumDeclarations.TcAnonFieldDecl cenv env parent emptyUnscopedTyparEnv (mkExceptionFieldName i) fdef)
+ TcRecdUnionAndEnumDeclarations.TcAnonFieldDecl cenv env parent emptyUnscopedTyparEnv (fun f -> f ()) (mkExceptionFieldName i) fdef)
TcRecdUnionAndEnumDeclarations.ValidateFieldNames(args, args')
let repr =
match reprIdOpt with
@@ -2624,6 +2664,8 @@ module EstablishTypeDefinitionCores =
let g = cenv.g
let env = AddDeclaredTypars CheckForDuplicateTypars (tycon.Typars) env
let env = MakeInnerEnvForTyconRef env thisTyconRef false
+ let ad = env.AccessRights
+ let spreadSrcTys = ResizeArray ()
[ match synTyconRepr with
| SynTypeDefnSimpleRepr.None _ -> ()
| SynTypeDefnSimpleRepr.Union (_, unionCases, _) ->
@@ -2667,13 +2709,31 @@ module EstablishTypeDefinitionCores =
errorR(Error(FSComp.SR.tcStructsMustDeclareTypesOfImplicitCtorArgsExplicitly(), m))
yield (ty, m)
- | SynTypeDefnSimpleRepr.Record (_, fields, _) ->
- for SynField(fieldType = ty; range = m) in fields do
+ | SynTypeDefnSimpleRepr.Record (_, fieldsAndSpreads, _) ->
+ let tcField (SynField (fieldType = ty; range = m)) =
let tyR, _ = TcTypeAndRecover cenv NoNewTypars NoCheckCxs ItemOccurrence.UseInType WarnOnIWSAM.Yes env tpenv ty
- yield (tyR, m)
+ (tyR, m), ignore
+
+ let tcSpread (SynTypeSpread (ty = ty; range = m)) =
+ let spreadSrcTy, _ = TcTypeAndRecover cenv NoNewTypars NoCheckCxs ItemOccurrence.UseInType WarnOnIWSAM.Yes env tpenv ty
+
+ if isRecdTy g spreadSrcTy then
+ spreadSrcTys.Add spreadSrcTy
+ ResolveRecordOrClassFieldsOfType cenv.nameResolver m ad spreadSrcTy false
+ |> List.choose (function
+ | Item.RecdField field -> Some (field.RecdField.Id.idText, (field.FieldType, m), ignore)
+ | _ -> None)
+ else
+ match tryDestAnonRecdTy g spreadSrcTy with
+ | ValueSome (anonInfo, tys) -> tys |> List.mapi (fun i ty -> (anonInfo.SortedNames[i], (ty, m), ignore))
+ | ValueNone -> []
+
+ // We must apply the spread shadowing logic here
+ // to get the correct set of field types.
+ yield! fieldsAndSpreads |> Spreads.Types.Records.check ignore tcField tcSpread
| _ ->
- () ]
+ () ], spreadSrcTys
let ComputeModuleOrNamespaceKind g isModule typeNames attribs nm =
if not isModule then (Namespace true)
@@ -2798,6 +2858,16 @@ module EstablishTypeDefinitionCores =
let innerTypeNames = TypeNamesInMutRecDecls cenv envForDecls decls
MutRecDefnsPhase2DataForModule (moduleTyAcc, moduleEntity), (innerParent, innerTypeNames, envForDecls)
+ /// Re-resolve type-parameter attributes once the recursive group's attribute types are
+ /// established. Phase1A resolves them tentatively with diagnostics suppressed; this runs in the
+ /// deferred fixup, mirroring the entity/field/union attribute fixups.
+ let private fixupTyparAttrs (cenv: cenv) env (synTypars: SynTyparDecl list) (typars: Typar list) =
+ (synTypars, typars) ||> List.iter2 (fun (SynTyparDecl (attributes = Attributes synAttrs)) tp ->
+ if not (isNil synAttrs) then
+ TcAttributes cenv env AttributeTargets.GenericParameter synAttrs
+ |> filterOutWellKnownAttribs cenv.g WellKnownEntityAttributes.MeasureAttribute WellKnownValAttributes.None
+ |> tp.SetAttribs)
+
/// Establish 'type C < T1... TN > = ...' including
/// - computing the mangled name for C
/// but
@@ -2805,7 +2875,10 @@ module EstablishTypeDefinitionCores =
let private TcTyconDefnCore_Phase1A_BuildInitialTycon (cenv: cenv) env parent (MutRecDefnsPhase1DataForTycon(synTyconInfo, synTyconRepr, _, preEstablishedHasDefaultCtor, hasSelfReferentialCtor, _)) =
let g = cenv.g
let (SynComponentInfo (_, TyparDecls synTypars, _, id, xmlDoc, preferPostfix, synVis, _)) = synTyconInfo
- let checkedTypars = TcTyparDecls cenv env synTypars
+ // In a recursive group a type-parameter's attribute type may be defined later in the group and
+ // not yet resolvable. Resolve tentatively with diagnostics suppressed; the deferred fixup
+ // re-resolves against the completed environment (see fixupTyparAttrs at the drain).
+ let checkedTypars = suppressErrorReporting (fun () -> TcTyparDecls cenv env synTypars)
id |> List.iter (CheckNamespaceModuleOrTypeName g)
match synTyconRepr with
@@ -3445,7 +3518,7 @@ module EstablishTypeDefinitionCores =
with RecoverableException exn -> errorRecovery exn m))
/// Establish the fields, dispatch slots and union cases of a type
- let private TcTyconDefnCore_Phase1G_EstablishRepresentation (cenv: cenv) envinner tpenv inSig (MutRecDefnsPhase1DataForTycon(_, synTyconRepr, _, _, _, _)) (tycon: Tycon) (attrs: Attribs) =
+ let private TcTyconDefnCore_Phase1G_EstablishRepresentation (cenv: cenv) envinner tpenv inSig (MutRecDefnsPhase1DataForTycon(_, synTyconRepr, _, _, _, _)) (tycon: Tycon) (attrs: Attribs) addFixup =
let g = cenv.g
let m = tycon.Range
try
@@ -3578,22 +3651,22 @@ module EstablishTypeDefinitionCores =
let item = Item.UnionCase(info, false)
CallNameResolutionSink cenv.tcSink (unionCase.Range, nenv, item, emptyTyparInst, ItemOccurrence.Binding, ad)
- let typeRepr, baseValOpt, safeInitInfo =
+ let (typeRepr, baseValOpt, safeInitInfo), recheck =
match synTyconRepr with
| SynTypeDefnSimpleRepr.Exception synExnDefnRepr ->
let parent = Parent (mkLocalTyconRef tycon)
TcExceptionDeclarations.TcExnDefnCore_Phase1G_EstablishRepresentation cenv envinner parent tycon synExnDefnRepr |> ignore
- TNoRepr, None, NoSafeInitInfo
+ (TNoRepr, None, NoSafeInitInfo), ignore
| SynTypeDefnSimpleRepr.None _ ->
hiddenReprChecks false
noAllowNullLiteralAttributeCheck()
if hasMeasureAttr then
let repr = TFSharpTyconRepr (Construct.NewEmptyFSharpTyconData TFSharpClass)
- repr, None, NoSafeInitInfo
+ (repr, None, NoSafeInitInfo), ignore
else
- TNoRepr, None, NoSafeInitInfo
+ (TNoRepr, None, NoSafeInitInfo), ignore
// This unfortunate case deals with "type x = A"
// In F# this only defines a new type if A is not in scope
@@ -3608,10 +3681,10 @@ module EstablishTypeDefinitionCores =
TcRecdUnionAndEnumDeclarations.CheckUnionCaseName cenv unionCaseName hasRQAAttribute
let unionCase = Construct.NewUnionCase unionCaseName [] thisTy [] XmlDoc.Empty tycon.Accessibility
writeFakeUnionCtorsToSink [ unionCase ]
- Construct.MakeUnionRepr [ unionCase ], None, NoSafeInitInfo
+ (Construct.MakeUnionRepr [ unionCase ], None, NoSafeInitInfo), ignore
| SynTypeDefnSimpleRepr.TypeAbbrev(ParserDetail.ErrorRecovery, _rhsType, _) ->
- TNoRepr, None, NoSafeInitInfo
+ (TNoRepr, None, NoSafeInitInfo), ignore
| SynTypeDefnSimpleRepr.TypeAbbrev(ParserDetail.Ok, rhsType, _) ->
if hasSealedAttr = Some true then
@@ -3622,12 +3695,12 @@ module EstablishTypeDefinitionCores =
let kind = if hasMeasureAttr then TyparKind.Measure else TyparKind.Type
let theTypeAbbrev, _ = TcTypeOrMeasureAndRecover (Some kind) cenv NoNewTypars CheckCxs ItemOccurrence.UseInType WarnOnIWSAM.No envinner tpenv rhsType
- TMeasureableRepr theTypeAbbrev, None, NoSafeInitInfo
+ (TMeasureableRepr theTypeAbbrev, None, NoSafeInitInfo), ignore
// If we already computed a representation, e.g. for a generative type definition, then don't change it here.
elif (match tycon.TypeReprInfo with TNoRepr -> false | _ -> true) then
- tycon.TypeReprInfo, None, NoSafeInitInfo
+ (tycon.TypeReprInfo, None, NoSafeInitInfo), ignore
else
- TNoRepr, None, NoSafeInitInfo
+ (TNoRepr, None, NoSafeInitInfo), ignore
| SynTypeDefnSimpleRepr.Union (_, unionCases, mRepr) ->
noMeasureAttributeCheck()
@@ -3637,35 +3710,154 @@ module EstablishTypeDefinitionCores =
structLayoutAttributeCheck false
let hasRQAAttribute = EntityHasWellKnownAttribute cenv.g WellKnownEntityAttributes.RequireQualifiedAccessAttribute tycon
- let unionCases = TcRecdUnionAndEnumDeclarations.TcUnionCaseDecls cenv envinner innerParent thisTy thisTyInst hasRQAAttribute tpenv unionCases
+ let unionCases = TcRecdUnionAndEnumDeclarations.TcUnionCaseDecls cenv envinner innerParent thisTy thisTyInst hasRQAAttribute tpenv addFixup unionCases
multiCaseUnionStructCheck unionCases
writeFakeUnionCtorsToSink unionCases
CallEnvSink cenv.tcSink (mRepr, envinner.NameEnv, ad)
let repr = Construct.MakeUnionRepr unionCases
- repr, None, NoSafeInitInfo
+ (repr, None, NoSafeInitInfo), ignore
- | SynTypeDefnSimpleRepr.Record (_, fields, mRepr) ->
+ | SynTypeDefnSimpleRepr.Record (_accessibility, fieldsAndSpreads, mRepr) ->
noMeasureAttributeCheck()
noSealedAttributeCheck FSComp.SR.tcTypesAreAlwaysSealedRecord
noAbstractClassAttributeCheck()
noAllowNullLiteralAttributeCheck()
structLayoutAttributeCheck true // these are allowed for records
- let recdFields = TcRecdUnionAndEnumDeclarations.TcNamedFieldDecls cenv envinner innerParent false tpenv fields
- recdFields |> CheckDuplicates (fun f -> f.Id) "field" |> ignore
- writeFakeRecordFieldsToSink recdFields
- CallEnvSink cenv.tcSink (mRepr, envinner.NameEnv, ad)
- let data =
- {
- fsobjmodel_cases = Construct.MakeUnionCases []
- fsobjmodel_kind = TFSharpRecord
- fsobjmodel_vslots = []
- fsobjmodel_rfields = Construct.MakeRecdFieldsTable recdFields
- }
+ let check pass =
+ let firstPass = pass = FirstPass
+ let recdFields =
+ let tcField synField =
+ let field = TcRecdUnionAndEnumDeclarations.TcNamedFieldDecl cenv envinner innerParent false tpenv addFixup synField |> Option.get
+ let errorAmbiguousShadowing () = if firstPass then errorR (Duplicate ("field", field.Id.idText, field.Id.idRange))
+ field, errorAmbiguousShadowing
+
+ let tcSpread (SynTypeSpread (ty = ty; range = m)) =
+ let mTy = ty.Range
+ let (spreadSrcTy, _tpenv), error =
+ try TcType cenv NoNewTypars CheckCxs ItemOccurrence.UseInType WarnOnIWSAM.Yes envinner tpenv ty, false with
+ | RecoverableException e ->
+ if firstPass then
+ errorRecovery e ty.Range
+ (g.obj_ty_ambivalent, tpenv), true
+
+ let spreadSrcTyIsNullable = g.checkNullness && (nullnessOfTy g spreadSrcTy).Evaluate() = NullnessInfo.WithNull
+ let spreadSrcTyIsRecd = error || isRecdTy g spreadSrcTy || isAnonRecdTy g spreadSrcTy
+
+ let isValidSpreadSrcTy = not spreadSrcTyIsNullable && spreadSrcTyIsRecd
+
+ if isValidSpreadSrcTy then
+ let spreadSrcTy =
+ tryAppTy g spreadSrcTy
+ |> ValueOption.map (fun (tcref, tinst) ->
+ let _, _, newTinst = FreshenTypeInst g m tcref.Typars
+ SolveTyparsEqualTypes g cenv.css m newTinst tinst
+ TType_app (tcref, newTinst, g.knownWithoutNull))
+ |> ValueOption.defaultValue spreadSrcTy
+
+ let recordFieldsFromSpread =
+ if isRecdTy g spreadSrcTy then
+ ResolveRecordOrClassFieldsOfType cenv.nameResolver m ad spreadSrcTy false
+ else
+ tryDestAnonRecdTy g spreadSrcTy
+ |> ValueOption.map (fun (anonInfo, tys) ->
+ anonInfo.SortedIds
+ |> List.ofArray
+ |> List.mapi (fun i id -> Item.AnonRecdField (anonInfo, tys, i, id.idRange)))
+ |> ValueOption.defaultValue []
+
+ recordFieldsFromSpread
+ |> List.choose (fun field ->
+ match field with
+ | Item.RecdField fieldInfo ->
+ // Update the field ID's range to be that of the spread.
+ let syntheticId = ident (fieldInfo.RecdField.Id.idText, mTy)
+ let fieldTy = fieldInfo.FieldType
+ let vis =
+ let vis, _ = ComputeAccessAndCompPath g envinner None mTy None None innerParent
+ combineAccess vis thisTyconRef.TypeReprAccessibility
+
+ let recdField =
+ { fieldInfo.RecdField with
+ rfield_id = syntheticId
+ rfield_type = fieldTy
+ rfield_access = vis }
+
+ let warnAmbiguousShadowing () =
+ let fmtedSpreadField = NicePrint.stringOfRecdField envinner.DisplayEnv cenv.infoReader fieldInfo.TyconRef recdField
+ let fmtedSpreadSrcTy = NicePrint.stringOfTy envinner.DisplayEnv spreadSrcTy
+ warning (Error (FSComp.SR.tcRecordTypeDefinitionSpreadFieldShadowsExplicitField (fmtedSpreadField, fmtedSpreadSrcTy), m))
+
+ Some (fieldInfo.RecdField.Id.idText, recdField, warnAmbiguousShadowing)
+
+ | Item.AnonRecdField (anonInfo, tys, fieldIndex, _) ->
+ let fieldId =
+ let orig = anonInfo.SortedIds[fieldIndex]
+ ident (orig.idText, m)
+
+ let ty = tys[fieldIndex]
+
+ let field =
+ let stat = false
+ let konst = None
+ let generated = false
+ let mut = false
+ let volatile = false
+ let pattribs = []
+ let fattribs = []
+ let vis = None
+ TcRecdUnionAndEnumDeclarations.MakeRecdFieldSpec g envinner innerParent (stat, konst, ty, pattribs, fattribs, fieldId, generated, mut, volatile, XmlDoc.Empty, vis, mTy)
+
+ let warnAmbiguousShadowing () =
+ let typars = tryAppTy g ty |> ValueOption.map (snd >> List.choose (tryDestTyparTy g >> ValueOption.toOption)) |> ValueOption.defaultValue []
+ let fmtedSpreadField = LayoutRender.showL (NicePrint.prettyLayoutOfMemberSig envinner.DisplayEnv ([], fieldId.idText, typars, [], ty))
+ let fmtedSpreadSrcTy = NicePrint.stringOfTy envinner.DisplayEnv spreadSrcTy
+ warning (Error (FSComp.SR.tcRecordTypeDefinitionSpreadFieldShadowsExplicitField (fmtedSpreadField, fmtedSpreadSrcTy), m))
+
+ Some (fieldId.idText, field, warnAmbiguousShadowing)
+
+ | _ -> None)
+ elif not firstPass then
+ []
+ else
+ if not ty.IsFromParseError then
+ if not spreadSrcTyIsRecd then
+ errorR (Error (FSComp.SR.tcRecordTypeDefinitionSpreadSourceMustBeRecord (), m))
+ elif spreadSrcTyIsNullable then
+ errorR (Error (FSComp.SR.tcRecordTypeDefinitionSpreadSourceCannotBeNullable (), m))
+ []
- let repr = TFSharpTyconRepr data
- repr, None, NoSafeInitInfo
+ let checkSpreadsLanguageFeature m =
+ if firstPass then
+ checkLanguageFeatureAndRecover g.langVersion LanguageFeature.RecordSpreads m
+
+ fieldsAndSpreads |> Spreads.Types.Records.check checkSpreadsLanguageFeature tcField tcSpread
+
+ writeFakeRecordFieldsToSink recdFields
+ CallEnvSink cenv.tcSink (mRepr, envinner.NameEnv, ad)
+
+ let data =
+ {
+ fsobjmodel_cases = Construct.MakeUnionCases []
+ fsobjmodel_kind = TFSharpRecord
+ fsobjmodel_vslots = []
+ fsobjmodel_rfields = Construct.MakeRecdFieldsTable recdFields
+ }
+
+ let repr = TFSharpTyconRepr data
+ repr, None, NoSafeInitInfo
+
+ let recheck =
+ if fieldsAndSpreads |> List.exists (function SynFieldOrSpread.Spread _ -> true | SynFieldOrSpread.Field _ -> false) then
+ fun () ->
+ let repr, _, _ = check SecondPass
+ tycon.entity_tycon_repr <- repr
+ else
+ ignore
+
+
+ check FirstPass, recheck
| SynTypeDefnSimpleRepr.LibraryOnlyILAssembly (s, _) ->
let s = (s :?> ILType)
@@ -3674,10 +3866,10 @@ module EstablishTypeDefinitionCores =
noAllowNullLiteralAttributeCheck()
structLayoutAttributeCheck false
noAbstractClassAttributeCheck()
- TAsmRepr s, None, NoSafeInitInfo
+ (TAsmRepr s, None, NoSafeInitInfo), ignore
| SynTypeDefnSimpleRepr.General (kind, inherits, slotsigs, fields, isConcrete, isIncrClass, implicitCtorSynPats, _) ->
- let userFields = TcRecdUnionAndEnumDeclarations.TcNamedFieldDecls cenv envinner innerParent isIncrClass tpenv fields
+ let userFields = TcRecdUnionAndEnumDeclarations.TcNamedFieldDecls cenv envinner innerParent isIncrClass tpenv addFixup fields
let implicitStructFields =
[ // For structs with an implicit ctor, determine the fields immediately based on the arguments
match implicitCtorSynPats with
@@ -3705,7 +3897,7 @@ module EstablishTypeDefinitionCores =
| SynTypeDefnKind.Opaque ->
hiddenReprChecks true
noAllowNullLiteralAttributeCheck()
- TNoRepr, None, NoSafeInitInfo
+ (TNoRepr, None, NoSafeInitInfo), ignore
| _ ->
// Note: for a mutually recursive set we can't check this condition
@@ -3828,7 +4020,7 @@ module EstablishTypeDefinitionCores =
fsobjmodel_rfields = Construct.MakeRecdFieldsTable (userFields @ implicitStructFields @ safeInitFields)
}
let repr = TFSharpTyconRepr data
- repr, baseValOpt, safeInitInfo
+ (repr, baseValOpt, safeInitInfo), ignore
| SynTypeDefnSimpleRepr.Enum (decls, m) ->
let fieldTy, fields' = TcRecdUnionAndEnumDeclarations.TcEnumDecls cenv envinner tpenv innerParent thisTy decls
@@ -3852,7 +4044,7 @@ module EstablishTypeDefinitionCores =
fsobjmodel_rfields = Construct.MakeRecdFieldsTable (vfld :: fields')
}
let repr = TFSharpTyconRepr data
- repr, None, NoSafeInitInfo
+ (repr, None, NoSafeInitInfo), ignore
tycon.entity_tycon_repr <- typeRepr
// We check this just after establishing the representation
@@ -3866,10 +4058,10 @@ module EstablishTypeDefinitionCores =
errorR(Error(FSComp.SR.tcConditionalAttributeUsage(), m))
| _ -> ()
- (baseValOpt, safeInitInfo)
+ baseValOpt, safeInitInfo, recheck
with RecoverableException exn ->
- errorRecovery exn m
- None, NoSafeInitInfo
+ errorRecovery exn m
+ None, NoSafeInitInfo, ignore
/// Check that a set of type definitions is free of cycles in abbreviations
let private TcTyconDefnCore_CheckForCyclicAbbreviations tycons =
@@ -4193,14 +4385,49 @@ module EstablishTypeDefinitionCores =
// be satisfied, so we have to do this prior to checking any constraints.
//
// First find all the field types in all the structural types
- let tyconsWithStructuralTypes =
- (envMutRecPrelim, withEnvs)
- ||> MutRecShapes.mapTyconsWithEnv (fun envForDecls (origInfo, tyconOpt) ->
- match origInfo, tyconOpt with
+ let tyconsWithStructuralTypesAndSpreadSources =
+ (envMutRecPrelim, withEnvs)
+ ||> MutRecShapes.mapTyconsWithEnv (fun envForDecls (origInfo, tyconOpt) ->
+ match origInfo, tyconOpt with
| (typeDefCore, _, _), Some tycon -> Some (tycon, GetStructuralElementsOfTyconDefn cenv envForDecls tpenv typeDefCore tycon)
- | _ -> None)
- |> MutRecShapes.collectTycons
+ | _ -> None)
+ |> MutRecShapes.collectTycons
|> List.choose id
+
+ let tyconsWithStructuralTypes =
+ [
+ for tycon, (tys, _) in tyconsWithStructuralTypesAndSpreadSources ->
+ tycon, tys
+ ]
+
+ // Check for cyclic spreads.
+ do
+ if cenv.g.langVersion.SupportsFeature LanguageFeature.RecordSpreads then
+ let (|PotentiallyRecursiveTycon|_|) ty =
+ tryTcrefOfAppTy cenv.g ty
+ |> ValueOption.bind _.TryDeref
+
+ let edges =
+ [
+ for dst, (_, spreadSrcs) in tyconsWithStructuralTypesAndSpreadSources do
+ for src in spreadSrcs do
+ match src with
+ | PotentiallyRecursiveTycon src -> dst, src
+ | _ -> ()
+ ]
+
+ let tycons =
+ let seen = HashSet ()
+ [
+ for dst, src in edges do
+ if seen.Add dst.Stamp then
+ yield dst
+ if seen.Add src.Stamp then
+ yield src
+ ]
+
+ let graph = Graph (_.Stamp, tycons, edges)
+ graph.IterateCycles (fun path -> errorR (Error (FSComp.SR.tcTypeDefinitionIsCyclicThroughSpreads (), (List.head path).Range)))
let scSet = TyconConstraintInference.InferSetOfTyconsSupportingComparable cenv envMutRecPrelim.DisplayEnv tyconsWithStructuralTypes
let seSet = TyconConstraintInference.InferSetOfTyconsSupportingEquatable cenv envMutRecPrelim.DisplayEnv tyconsWithStructuralTypes
@@ -4240,18 +4467,65 @@ module EstablishTypeDefinitionCores =
// Now do the representations. Each baseValOpt is a residue from the representation which is potentially available when
// checking the members.
let withBaseValsAndSafeInitInfos =
- (envMutRecPrelim, withAttrs) ||> MutRecShapes.mapTyconsWithEnv (fun envForDecls (origInfo, tyconAndAttrsOpt) ->
- let info =
- match origInfo, tyconAndAttrsOpt with
- | (typeDefCore, _, _), Some (tycon, (attrs, _)) -> TcTyconDefnCore_Phase1G_EstablishRepresentation cenv envForDecls tpenv inSig typeDefCore tycon attrs
- | _ -> None, NoSafeInitInfo
- let tyconOpt, fixupFinalAttrs =
- match tyconAndAttrsOpt with
- | None -> None, (fun () -> ())
- | Some (tycon, (_prelimAttrs, getFinalAttrs)) -> Some tycon, (fun () -> tycon.entity_attribs <- WellKnownEntityAttribs.Create(getFinalAttrs()))
-
- (origInfo, tyconOpt, fixupFinalAttrs, info))
-
+ let passOne =
+ (envMutRecPrelim, withAttrs) ||> MutRecShapes.mapTyconsWithEnv (fun envForDecls (origInfo, tyconAndAttrsOpt) ->
+ let info, tyconOpt, fixupFinalAttrs =
+ match origInfo, tyconAndAttrsOpt with
+ | (typeDefCore, _, _), Some (tycon, (attrs, getFinalAttrs)) ->
+ let fixups = ResizeArray()
+ let info = TcTyconDefnCore_Phase1G_EstablishRepresentation cenv envForDecls tpenv inSig typeDefCore tycon attrs fixups.Add
+ let (MutRecDefnsPhase1DataForTycon(SynComponentInfo(typeParams=TyparDecls synTypars), _, _, _, _, _)) = typeDefCore
+ let fixupFinalAttrs () =
+ tycon.entity_attribs <- WellKnownEntityAttribs.Create(getFinalAttrs())
+ fixupTyparAttrs cenv envForDecls synTypars tycon.Typars
+ for fixup in fixups do fixup()
+ info, Some tycon, fixupFinalAttrs
+ | _ -> (None, NoSafeInitInfo, ignore), None, ignore
+
+ (origInfo, tyconOpt, fixupFinalAttrs, info))
+
+ let rechecks =
+ [
+ for _, tyconOpt, _, (_, _, recheck) in passOne |> MutRecShapes.collectTycons do
+ match tyconOpt with
+ | Some tycon -> tycon.Stamp, recheck
+ | None -> ()
+ ]
+
+ let spreadDependencies =
+ Map.ofList [
+ for tycon, (_, spreadSrcTys) in tyconsWithStructuralTypesAndSpreadSources ->
+ tycon.Stamp, [
+ for ty in spreadSrcTys do
+ match tryTcrefOfAppTy cenv.g ty |> ValueOption.bind _.TryDeref with
+ | ValueSome tycon -> tycon.Stamp
+ | ValueNone -> ()
+ ]
+ ]
+
+ let recheckMap = Map.ofList rechecks
+ let seen = HashSet ()
+
+ let rec recheck tyconStamp =
+ if seen.Add tyconStamp then
+ match spreadDependencies |> Map.tryFind tyconStamp with
+ | Some spreadSrcStamps ->
+ for spreadSrcStamp in spreadSrcStamps do
+ if recheckMap |> Map.containsKey spreadSrcStamp then
+ recheck spreadSrcStamp
+ | None -> ()
+
+ match recheckMap |> Map.tryFind tyconStamp with
+ | Some recheck -> recheck ()
+ | None -> ()
+
+ // Spreads require a second pass once all fields in the group are known.
+ for tyconStamp, _ in rechecks do
+ recheck tyconStamp
+
+ passOne |> MutRecShapes.mapTycons (fun (origInfo, tyconOpt, fixupFinalAttrs, (v, safeInit, _)) ->
+ (origInfo, tyconOpt, fixupFinalAttrs, (v, safeInit)))
+
// Now check for cyclic structs and inheritance. It's possible these should be checked as separate conditions.
// REVIEW: checking for cyclic inheritance is happening too late. See note above.
TcTyconDefnCore_CheckForCyclicStructsAndInheritance cenv tycons
@@ -4771,8 +5045,9 @@ module TcDeclarations =
// We put the hash/compare bindings before the type definitions and the
// equality bindings after because tha is the order they've always been generated
// in, and there are code generation tests to check that.
- let binds = AddAugmentationDeclarations.AddGenericHashAndComparisonBindings cenv tycon
+ let binds = AddAugmentationDeclarations.AddGenericHashAndComparisonBindings cenv tycon
let binds3 = AddAugmentationDeclarations.AddGenericEqualityBindings cenv envForDecls tycon
+ let binds5 = AddAugmentationDeclarations.AddReflectionFreeToStringBindings(cenv, envForDecls, tycon)
let binds4 =
if tycon.IsUnionTycon && AddAugmentationDeclarations.ShouldAugmentUnion g tycon then
let unionVals =
@@ -4782,7 +5057,7 @@ module TcDeclarations =
AugmentTypeDefinitions.MakeBindingsForUnionAugmentation g tycon (List.map mkLocalValRef unionVals)
else
[]
- binds@binds4, binds3)
+ binds@binds4, binds3@binds5)
// Check for cyclic structs and inheritance all over again, since we may have added some fields to the struct when generating the implicit construction syntax
EstablishTypeDefinitionCores.TcTyconDefnCore_CheckForCyclicStructsAndInheritance cenv tycons
@@ -4938,6 +5213,10 @@ module TcDeclarations =
let mutRecDefnsAfterVals = TcMutRecSignatureDecls_Phase2 cenv scopem envMutRecPrelimWithReprs withEnvs
+ // Now the sibling types and their constructors are established, re-resolve any attributes
+ // that referred to them (mirrors the implementation path in TcMutRecDefns_Phase2_Bindings).
+ mutRecDefnsAfterCore |> MutRecShapes.iterTycons (fun (_, _, fixupFinalAttrs, _, _) -> fixupFinalAttrs())
+
// Updates the types of the modules to contain the contents so far, which now includes values and members
MutRecBindingChecking.TcMutRecDefns_UpdateModuleContents mutRecNSInfo mutRecDefnsAfterVals
diff --git a/src/Compiler/Checking/CheckFormatStrings.fs b/src/Compiler/Checking/CheckFormatStrings.fs
index 70608224578..d768dc9e47d 100644
--- a/src/Compiler/Checking/CheckFormatStrings.fs
+++ b/src/Compiler/Checking/CheckFormatStrings.fs
@@ -37,6 +37,9 @@ let mkFlexibleDecimalFormatTypar (g: TcGlobals) m =
let mkFlexibleFloatFormatTypar (g: TcGlobals) m =
mkFlexibleFormatTypar g m [ g.float_ty; g.float32_ty; g.decimal_ty ] g.float_ty
+let stringFormatTy (g: TcGlobals) =
+ if g.checkNullness && g.langFeatureNullness then g.string_ty_ambivalent else g.string_ty
+
type FormatInfoRegister =
{ mutable leftJustify : bool
mutable numPrefixIfPos : char option
@@ -448,8 +451,7 @@ let parseFormatStringInternal
checkOtherFlags ch
collectSpecifierLocation fragLine fragCol 1
let i = skipPossibleInterpolationHole (i+1)
- let stringTy = if g.checkNullness && g.langFeatureNullness then g.string_ty_ambivalent else g.string_ty
- parseLoop ((posi, stringTy) :: acc) (i, fragLine, fragCol+1) fragments
+ parseLoop ((posi, stringFormatTy g) :: acc) (i, fragLine, fragCol+1) fragments
| 'O' ->
checkOtherFlags ch
diff --git a/src/Compiler/Checking/CheckFormatStrings.fsi b/src/Compiler/Checking/CheckFormatStrings.fsi
index eb8120f712d..a581f26be8f 100644
--- a/src/Compiler/Checking/CheckFormatStrings.fsi
+++ b/src/Compiler/Checking/CheckFormatStrings.fsi
@@ -12,6 +12,15 @@ open FSharp.Compiler.TcGlobals
open FSharp.Compiler.Text
open FSharp.Compiler.TypedTree
+/// A flexible type variable constrained to the integer types accepted by the '%d'/'%i'/'%u' specifiers.
+val mkFlexibleIntFormatTypar: g: TcGlobals -> m: range -> TType
+
+/// A flexible type variable constrained to 'decimal', as accepted by the '%M' specifier.
+val mkFlexibleDecimalFormatTypar: g: TcGlobals -> m: range -> TType
+
+/// The type accepted by the '%s' specifier: ambivalent about nullness when nullness is checked.
+val stringFormatTy: g: TcGlobals -> TType
+
val ParseFormatString:
m: range ->
fragmentRanges: range list ->
diff --git a/src/Compiler/Checking/CheckPatterns.fs b/src/Compiler/Checking/CheckPatterns.fs
index 55295562303..d7b1ffd4e3e 100644
--- a/src/Compiler/Checking/CheckPatterns.fs
+++ b/src/Compiler/Checking/CheckPatterns.fs
@@ -498,13 +498,18 @@ and TcPatArrayOrList warnOnUpper cenv env vFlags patEnv ty isArray args m =
phase2, acc
and TcRecordPat warnOnUpper (cenv: cenv) env vFlags patEnv ty fieldPats m =
- let fieldPats =
+ let idents =
+ let (|Last|) = List.last
+ fieldPats
+ |> List.map (fun (NamePatPairField (fieldName = SynLongIdent (id = Last fieldId))) -> fieldId)
+
+ let fieldPats =
fieldPats
- |> List.map (fun (NamePatPairField(fieldName = fieldLid; pat = pat)) ->
- match fieldLid.LongIdent with
- | [id] -> ([], id), pat
- | lid -> List.frontAndBack lid, pat)
+ |> List.map (fun (NamePatPairField(fieldName = fieldLid; pat = pat)) ->
+ let path, fieldId = List.frontAndBack fieldLid.LongIdent
+ fieldId, ExplicitOrSpread.Explicit (path, pat))
+ CheckRecdExprDuplicateFields idents
match BuildFieldMap cenv env false ty fieldPats m with
| None -> (fun _ -> TPat_error m), patEnv
| Some(tinst, tcref, fldsmap, _fldsList) ->
@@ -520,13 +525,14 @@ and TcRecordPat warnOnUpper (cenv: cenv) env vFlags patEnv ty fieldPats m =
let fieldPats, patEnvR =
(patEnv, ftys) ||> List.mapFold (fun s (ty, fsp) ->
match fldsmap.TryGetValue fsp.rfield_id.idText with
- | true, v ->
+ | true, ExplicitOrSpread.Explicit v ->
let warnOnUpper =
if cenv.g.langVersion.SupportsFeature(LanguageFeature.DontWarnOnUppercaseIdentifiersInBindingPatterns) then
AllIdsOK
else
warnOnUpper
TcPat warnOnUpper cenv env None vFlags s ty v
+ | true, ExplicitOrSpread.Spread _ -> (* Unreachable. *) error (InternalError ("Spreads in patterns are not supported.", m))
| _ -> (fun _ -> TPat_wild m), s)
let phase2 values =
diff --git a/src/Compiler/Checking/CheckRecordSyntaxHelpers.fs b/src/Compiler/Checking/CheckRecordSyntaxHelpers.fs
index b973bc17286..1df28906810 100644
--- a/src/Compiler/Checking/CheckRecordSyntaxHelpers.fs
+++ b/src/Compiler/Checking/CheckRecordSyntaxHelpers.fs
@@ -2,6 +2,7 @@
module internal FSharp.Compiler.CheckRecordSyntaxHelpers
+open System
open FSharp.Compiler.CheckBasics
open FSharp.Compiler.DiagnosticsLogger
open FSharp.Compiler.Features
@@ -14,47 +15,6 @@ open FSharp.Compiler.TypedTree
open FSharp.Compiler.Xml
open FSharp.Compiler.SyntaxTrivia
-/// Merges updates to nested record fields on the same level in record copy-and-update.
-///
-/// `TransformAstForNestedUpdates` expands `{ x with A.B = 10; A.C = "" }`
-///
-/// into
-///
-/// { x with
-/// A = { x.A with B = 10 };
-/// A = { x.A with C = "" }
-/// }
-///
-/// which we here convert to
-///
-/// { x with A = { x.A with B = 10; C = "" } }
-let GroupUpdatesToNestedFields (fields: ((Ident list * Ident) * SynExpr option) list) =
- let rec groupIfNested res xs =
- match xs with
- | [] -> res
- | [ x ] -> x :: res
- | x :: y :: ys ->
- match x, y with
- | (lidwid, Some(SynExpr.Record(baseInfo, copyInfo, fields1, m))), (_, Some(SynExpr.Record(recordFields = fields2))) ->
- let reducedRecd =
- (lidwid, Some(SynExpr.Record(baseInfo, copyInfo, fields1 @ fields2, m)))
-
- groupIfNested res (reducedRecd :: ys)
- | (lidwid, Some(SynExpr.AnonRecd(isStruct, copyInfo, fields1, m, trivia))), (_, Some(SynExpr.AnonRecd(recordFields = fields2))) ->
- let reducedRecd =
- (lidwid, Some(SynExpr.AnonRecd(isStruct, copyInfo, fields1 @ fields2, m, trivia)))
-
- groupIfNested res (reducedRecd :: ys)
- | _ -> groupIfNested (x :: res) (y :: ys)
-
- fields
- |> List.groupBy (fun ((_, field), _) -> field.idText)
- |> List.collect (fun (_, fields) ->
- if fields.Length < 2 then
- fields
- else
- groupIfNested [] fields)
-
/// Expands a long identifier into nested copy-and-update expressions.
///
/// `{ x with A.B = 0; A.C = "" }` becomes `{ x with A = { x.A with B = 0 }; A = { x.A with C = "" } }`
@@ -122,17 +82,27 @@ let TransformAstForNestedUpdates (cenv: TcFileState) (env: TcEnv) overallTy (lid
| Item.AnonRecdField(
anonInfo = {
AnonRecdTypeInfo.TupInfo = TupInfo.Const isStruct
- }) ->
- let fields = [ LongIdentWithDots([ fieldId ], []), None, nestedField ]
+ }
+ range = m) ->
+ let fields =
+ [
+ SynExprAnonRecordFieldOrSpread.Field(
+ SynExprAnonRecordField(LongIdentWithDots([ fieldId ], []), None, nestedField, m),
+ None
+ )
+ ]
+
SynExpr.AnonRecd(isStruct, copyInfo outerFieldId, fields, outerFieldId.idRange, { OpeningBraceRange = range0 })
| _ ->
let fields =
[
- SynExprRecordField(
- (LongIdentWithDots([ fieldId ], []), true),
- None,
- Some nestedField,
- unionRanges fieldId.idRange nestedField.Range,
+ SynExprRecordFieldOrSpread.Field(
+ SynExprRecordField(
+ (LongIdentWithDots([ fieldId ], []), true),
+ None,
+ Some nestedField,
+ unionRanges fieldId.idRange nestedField.Range
+ ),
None
)
]
@@ -149,7 +119,7 @@ let TransformAstForNestedUpdates (cenv: TcFileState) (env: TcEnv) overallTy (lid
match access, fields with
| _, [] -> failwith "unreachable"
- | accessIds, [ (fieldId, _) ] -> (accessIds, fieldId), Some exprBeingAssigned
+ | accessIds, [ (fieldId, _) ] -> (accessIds, fieldId), exprBeingAssigned
| accessIds, (outerFieldId, item) :: rest ->
checkLanguageFeatureAndRecover cenv.g.langVersion LanguageFeature.NestedCopyAndUpdate (rangeOfLid lid)
@@ -157,22 +127,20 @@ let TransformAstForNestedUpdates (cenv: TcFileState) (env: TcEnv) overallTy (lid
let outerFieldId = ident (outerFieldId.idText, outerFieldId.idRange.MakeSynthetic())
- (accessIds, outerFieldId),
- Some(synExprRecd (recdExprCopyInfo (fields |> List.map fst) withExpr) outerFieldId rest exprBeingAssigned)
+ (accessIds, outerFieldId), synExprRecd (recdExprCopyInfo (fields |> List.map fst) withExpr) outerFieldId rest exprBeingAssigned
/// This name is used when a complex expression is bound for use as a binding in a copy-and-update expression.
/// For example, in `{ f () with ... }`, `f ()` is replaced by `let bind@ = f ()`
let BindIdText = "bind@"
/// Finding the 'bind@' identifier is the only way to detect that an expression has already been bound.
-let inline (|IsSimpleOrBoundExpr|_|) (withExprOpt: (SynExpr * BlockSeparator) option) =
- match withExprOpt with
- | None -> true
- | Some(expr, _) ->
- match expr with
- | SynExpr.LongIdent(_, lIds, _, _) -> lIds.LongIdent |> List.exists (fun id -> id.idText = BindIdText)
- | SynExpr.Ident _ -> true
- | _ -> false
+let inline (|IsSimpleOrBoundExpr|_|) (withExpr: SynExpr) =
+ match withExpr with
+ | SynExpr.LongIdent(_, lIds, _, _) ->
+ lIds.LongIdent
+ |> List.exists _.idText.StartsWith(BindIdText, StringComparison.Ordinal)
+ | SynExpr.Ident _ -> true
+ | _ -> false
/// When the original expression in copy-and-update is more complex than `{ x with ... }`, like `{ f () with ... }`,
/// we bind it first, so that it's not evaluated multiple times during a nested update
@@ -209,3 +177,42 @@ let BindOriginalRecdExpr (withExpr: SynExpr * BlockSeparator) mkRecdExpr =
Range = mOrigExprSynth
Trivia = SynLetOrUseTrivia.Zero
}
+
+let mutable private bindId = 0
+
+let private newBindId () =
+ System.Threading.Interlocked.Increment &bindId
+
+let bindSrcIn (spreadSrcExpr: SynExpr) =
+ let mOrigExprSynth = spreadSrcExpr.Range.MakeSynthetic()
+ let id = mkSynId mOrigExprSynth $"%s{BindIdText}-%d{newBindId ()}"
+ let newSpreadSrcExpr = SynExpr.Ident id
+
+ let binding =
+ mkSynBinding
+ (PreXmlDoc.Empty, mkSynPatVar None id)
+ (None,
+ false,
+ false,
+ mOrigExprSynth,
+ DebugPointAtBinding.NoneAtSticky,
+ None,
+ spreadSrcExpr,
+ mOrigExprSynth,
+ [],
+ [],
+ None,
+ SynBindingTrivia.Zero)
+
+ fun mkBody ->
+ SynExpr.LetOrUse
+ {
+ IsRecursive = false
+ //isUse = false,
+ IsFromSource = false // compiler generated during desugaring
+ // isBang = false,
+ Bindings = [ binding ]
+ Body = mkBody newSpreadSrcExpr
+ Range = mOrigExprSynth
+ Trivia = SynLetOrUseTrivia.Zero
+ }
diff --git a/src/Compiler/Checking/CheckRecordSyntaxHelpers.fsi b/src/Compiler/Checking/CheckRecordSyntaxHelpers.fsi
index dc68f8a73e2..c8457832087 100644
--- a/src/Compiler/Checking/CheckRecordSyntaxHelpers.fsi
+++ b/src/Compiler/Checking/CheckRecordSyntaxHelpers.fsi
@@ -7,9 +7,6 @@ open FSharp.Compiler.Syntax
open FSharp.Compiler.Text
open FSharp.Compiler.TypedTree
-val GroupUpdatesToNestedFields:
- fields: ((Ident list * Ident) * SynExpr option) list -> ((Ident list * Ident) * SynExpr option) list
-
val TransformAstForNestedUpdates<'a> :
cenv: TcFileState ->
env: TcEnv ->
@@ -17,11 +14,13 @@ val TransformAstForNestedUpdates<'a> :
lid: LongIdent ->
exprBeingAssigned: SynExpr ->
withExpr: SynExpr * (range * 'a) ->
- (Ident list * Ident) * SynExpr option
+ (Ident list * Ident) * SynExpr
val BindIdText: string
-val inline (|IsSimpleOrBoundExpr|_|): withExprOpt: (SynExpr * BlockSeparator) option -> bool
+val inline (|IsSimpleOrBoundExpr|_|): withExpr: SynExpr -> bool
val BindOriginalRecdExpr:
withExpr: SynExpr * BlockSeparator -> mkRecdExpr: ((SynExpr * BlockSeparator) option -> SynExpr) -> SynExpr
+
+val bindSrcIn: spreadSrcExpr: SynExpr -> ((SynExpr -> SynExpr) -> SynExpr)
diff --git a/src/Compiler/Checking/ConstraintSolver.fs b/src/Compiler/Checking/ConstraintSolver.fs
index ca4fe23ae79..dda55156397 100644
--- a/src/Compiler/Checking/ConstraintSolver.fs
+++ b/src/Compiler/Checking/ConstraintSolver.fs
@@ -1161,7 +1161,7 @@ and SolveTyparEqualsType (csenv: ConstraintSolverEnv) ndeep m2 (trace: OptionalT
}
// Like SolveTyparEqualsType but asserts all typar equalities simultaneously instead of one by one
-and SolveTyparsEqualTypes (csenv: ConstraintSolverEnv) ndeep m2 (trace: OptionalTrace) tpTys tys =
+and SolveTyparsEqualTypesAux (csenv: ConstraintSolverEnv) ndeep m2 (trace: OptionalTrace) tpTys tys =
trackErrors {
do! Iterate2D (
fun tpTy ty ->
@@ -4340,7 +4340,7 @@ let CodegenWitnessesForTyparInst tcVal g amap m typars tyargs =
let csenv = MakeConstraintSolverEnv ContextInfo.NoContext css m (DisplayEnv.Empty g)
let ftps, _renaming, tinst = FreshenTypeInst g m typars
let traitInfos = GetTraitConstraintInfosOfTypars g ftps
- let! _res = SolveTyparsEqualTypes csenv 0 m NoTrace tinst tyargs
+ let! _res = SolveTyparsEqualTypesAux csenv 0 m NoTrace tinst tyargs
return GenWitnessArgs amap g m traitInfos
}
@@ -4418,3 +4418,8 @@ let IsApplicableMethApprox g amap m (minfo: MethInfo) availObjTy =
| _ -> true
else
true
+
+let SolveTyparsEqualTypes g (css: ConstraintSolverState) m (typars: TypeInst) (tys: TypeInst) =
+ let csenv = MakeConstraintSolverEnv ContextInfo.NoContext css m (DisplayEnv.Empty g)
+ SolveTyparsEqualTypesAux csenv 0 m NoTrace typars tys
+ |> CommitOperationResult
diff --git a/src/Compiler/Checking/ConstraintSolver.fsi b/src/Compiler/Checking/ConstraintSolver.fsi
index eebd72c2e60..ec9cd0d515f 100644
--- a/src/Compiler/Checking/ConstraintSolver.fsi
+++ b/src/Compiler/Checking/ConstraintSolver.fsi
@@ -380,3 +380,6 @@ val ChooseTyparSolutionAndSolve: ConstraintSolverState -> DisplayEnv -> Typar ->
val IsApplicableMethApprox: TcGlobals -> ImportMap -> range -> MethInfo -> TType -> bool
val CanonicalizePartialInferenceProblem: ConstraintSolverState -> DisplayEnv -> range -> Typars -> unit
+
+val SolveTyparsEqualTypes:
+ g: TcGlobals -> css: ConstraintSolverState -> m: range -> typars: TypeInst -> tys: TypeInst -> unit
diff --git a/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs b/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs
index 8c2b84011f3..040b61f9a89 100644
--- a/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs
+++ b/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs
@@ -1013,6 +1013,112 @@ let requireBuilderMethod methodName ceenv m1 m2 =
if not (hasBuilderMethod ceenv m1 methodName) then
error (Error(FSComp.SR.tcRequireBuilderMethod methodName, m2))
+/// One `let`/`use`/`let!`/`use!`/`do!` binding step, exposing whether it is a "bang" construct, its
+/// continuation body, and how to rebuild the step around a rewritten body.
+let (|CeBindingStep|_|) expr =
+ match expr with
+ | SynExpr.LetOrUse({ IsRecursive = false } as data) ->
+ Some(data.IsBang, data.Body, (fun body -> SynExpr.LetOrUse { data with Body = body }))
+ | SynExpr.Sequential(sp, isTrueSeq, (SynExpr.DoBang _ as doBang), body, mSeq, trivia) ->
+ Some(true, body, (fun body -> SynExpr.Sequential(sp, isTrueSeq, doBang, body, mSeq, trivia)))
+ | _ -> None
+
+/// A function or constructor-pattern binding (`let f x = ...`, `let (Some x) = ...`) is not a simple
+/// value binding. At this stage both take the same shape (`SynPat.LongIdent` with argument patterns or
+/// type parameters), and neither should be treated as the pattern of a `let!`.
+let private isSimpleValuePat pat =
+ match pat with
+ | SynPat.LongIdent(argPats = SynArgPats.Pats(_ :: _))
+ | SynPat.LongIdent(argPats = SynArgPats.NamePatPairs(pats = _ :: _))
+ | SynPat.LongIdent(typarDecls = Some _) -> false
+ | _ -> true
+
+/// #19457: a plain `let p = rhs` inside a computation expression, where `rhs` contains a bang construct
+/// (`let!`/`use!`/`do!`), is rebound as `let! p = builder { rhs }`. Running the rhs as a nested
+/// computation of the same builder keeps its bindings scoped there rather than leaking past `p`.
+/// Returns None (leaving the ordinary `let` path) unless the rhs is a simple value binding whose spine
+/// reaches a bang.
+let tryRebindCeLetWithBangRhs (ceenv: ComputationExpressionContext<'a>) isRec m trivia binds innerComp : SynExpr option =
+ // A leading paren or return-type annotation belongs to the `let` binding, not to the nested
+ // computation: parens are not valid computation-expression body syntax, and the type is carried onto
+ // the `let!` pattern by mkTypedHeadPat. Strip them to get the computation the user actually wrote.
+ let rec coreOf expr =
+ match expr with
+ | SynExpr.Paren(expr = e)
+ | SynExpr.Typed(expr = e) -> coreOf e
+ | e -> e
+
+ // Does the binding spine reach a bang? This must mirror where `returnify` descends, so the gate and
+ // the transformation agree: plain lets, a leading statement, paren/type annotations, and the branches
+ // of an `if`/`match`. `try` (and `match!`) stay out — `returnify` treats a `try` as a value leaf, so a
+ // bang only inside a `try` is deliberately left reporting FS0750.
+ let rec spineHasBang expr =
+ match expr with
+ | CeBindingStep(isBang, body, _) -> isBang || spineHasBang body
+ | SynExpr.Sequential(expr2 = e2) -> spineHasBang e2
+ | SynExpr.Paren(expr = e)
+ | SynExpr.Typed(expr = e) -> spineHasBang e
+ | SynExpr.IfThenElse(thenExpr = th; elseExpr = el) -> spineHasBang th || Option.exists spineHasBang el
+ | SynExpr.Match(clauses = cs) -> cs |> List.exists (fun (SynMatchClause(resultExpr = r)) -> spineHasBang r)
+ | _ -> false
+
+ // Make the nested computation produce its final value: wrap plain value leaves in `return`, leaving
+ // constructs that already produce in the computation untouched, and pushing through lets, sequencing,
+ // `if` and `match` to reach the leaves. Loops (`while`/`for`) and the bang constructs are left as-is:
+ // they produce unit (or their own value) directly. A `try`, by contrast, is an ordinary value
+ // expression here, so it takes the leaf path and is returned as a whole (`return (try ...)`).
+ let rec returnify expr =
+ match expr with
+ | CeBindingStep(_, body, rebuild) -> rebuild (returnify body)
+ | SynExpr.Paren(expr = e) -> returnify e
+ | SynExpr.Sequential(sp, isTrueSeq, e1, e2, ms, tr) -> SynExpr.Sequential(sp, isTrueSeq, e1, returnify e2, ms, tr)
+ | SynExpr.IfThenElse(g, th, el, sp, r, mi, tr) -> SynExpr.IfThenElse(g, returnify th, Option.map returnify el, sp, r, mi, tr)
+ | SynExpr.Match(sp, e, clauses, mm, tr) ->
+ let clauses =
+ clauses
+ |> List.map (fun (SynMatchClause(p, w, res, mc, dp, ctr)) -> SynMatchClause(p, w, returnify res, mc, dp, ctr))
+
+ SynExpr.Match(sp, e, clauses, mm, tr)
+ | SynExpr.YieldOrReturn _
+ | SynExpr.YieldOrReturnFrom _
+ | SynExpr.DoBang _
+ | SynExpr.MatchBang _
+ | SynExpr.WhileBang _
+ | SynExpr.While _
+ | SynExpr.For _
+ | SynExpr.ForEach _ -> expr
+ | leaf -> SynExpr.YieldOrReturn((false, true), leaf, leaf.Range, SynExprYieldOrReturnTrivia.Zero)
+
+ // Only a single, non-inline, non-mutable, non-recursive plain 'let' binding to a simple value pattern
+ // whose spine reaches a bang is rewritten. A 'use', a bang buried inside a 'try', and a 'match!' are
+ // deliberately out of scope and keep reporting FS0750. `spineHasBang` and `returnify` walk the same
+ // spine (lets, sequencing, and if/match branches) so the gate and the rewrite agree.
+ match binds with
+ | [ SynBinding(headPat = pat; isInline = false; isMutable = false; expr = rhs; debugPoint = spBind) as binding ] when
+ not (ceenv.isQuery || isRec) && isSimpleValuePat pat && spineHasBang rhs
+ ->
+ let core = coreOf rhs
+ let mCe = core.Range
+ let builder = mkSynIdGet mCe ceenv.builderValName
+
+ let nestedCe =
+ SynExpr.App(ExprAtomicFlag.NonAtomic, false, builder, SynExpr.ComputationExpr(false, returnify core, mCe), mCe)
+
+ let letBang = mkSynLetBangBinding mCe (mkTypedHeadPat binding) nestedCe spBind m
+
+ Some(
+ SynExpr.LetOrUse
+ {
+ IsRecursive = false
+ IsFromSource = false
+ Bindings = [ letBang ]
+ Body = innerComp
+ Range = m
+ Trivia = trivia
+ }
+ )
+ | _ -> None
+
///
/// Try translate the syntax sugar
///
@@ -1454,24 +1560,7 @@ let rec TryTranslateComputationExpression
let setCondExpr = SynExpr.Set(SynExpr.Ident idCond, SynExpr.Ident idFirst, mGuard)
let binding =
- SynBinding(
- accessibility = None,
- kind = SynBindingKind.Normal,
- isInline = false,
- isMutable = false,
- attributes = [],
- xmlDoc = PreXmlDoc.Empty,
- valData = SynInfo.emptySynValData,
- headPat = patFirst,
- returnInfo = None,
- expr = guardExpr,
- range = guardExpr.Range,
- debugPoint = DebugPointAtBinding.NoneAtSticky,
- trivia =
- { SynBindingTrivia.Zero with
- LeadingKeyword = SynLeadingKeyword.LetBang mGuard
- }
- )
+ mkSynLetBangBinding mGuard patFirst guardExpr DebugPointAtBinding.NoneAtSticky guardExpr.Range
let bindCondExpr =
SynExpr.LetOrUse
@@ -1514,24 +1603,7 @@ let rec TryTranslateComputationExpression
}
let binding =
- SynBinding(
- accessibility = None,
- kind = SynBindingKind.Normal,
- isInline = false,
- isMutable = false,
- attributes = [],
- xmlDoc = PreXmlDoc.Empty,
- valData = SynInfo.emptySynValData,
- headPat = patFirst,
- returnInfo = None,
- expr = guardExpr,
- range = guardExpr.Range,
- debugPoint = DebugPointAtBinding.NoneAtSticky,
- trivia =
- { SynBindingTrivia.Zero with
- LeadingKeyword = SynLeadingKeyword.LetBang mGuard
- }
- )
+ mkSynLetBangBinding mGuard patFirst guardExpr DebugPointAtBinding.NoneAtSticky guardExpr.Range
SynExpr.LetOrUse
{
@@ -1733,24 +1805,7 @@ let rec TryTranslateComputationExpression
| DebugPointAtSequential.SuppressNeither -> DebugPointAtBinding.Yes mKeyword
let binding =
- SynBinding(
- accessibility = None,
- kind = SynBindingKind.Normal,
- isInline = false,
- isMutable = false,
- attributes = [],
- xmlDoc = PreXmlDoc.Empty,
- valData = SynInfo.emptySynValData,
- headPat = SynPat.Const(SynConst.Unit, rhsExpr.Range),
- returnInfo = None,
- expr = rhsExpr,
- range = rhsExpr.Range,
- debugPoint = sp,
- trivia =
- { SynBindingTrivia.Zero with
- LeadingKeyword = SynLeadingKeyword.LetBang mKeyword
- }
- )
+ mkSynLetBangBinding mKeyword (SynPat.Const(SynConst.Unit, rhsExpr.Range)) rhsExpr sp rhsExpr.Range
Some(
TranslateComputationExpression
@@ -1847,51 +1902,57 @@ let rec TryTranslateComputationExpression
false,
false) ->
- // For 'query' check immediately
- if ceenv.isQuery then
- match (List.map (BindingNormalization.NormalizeBinding ValOrMemberBinding cenv ceenv.env) binds) with
- | [ NormalizedBinding(_, SynBindingKind.Normal, false, false, _, _, _, _, _, _, _, _) ] when not isRec -> ()
- | normalizedBindings ->
- let failAt m =
- error (Error(FSComp.SR.tcNonSimpleLetBindingInQuery (), m))
+ // #19457: a plain 'let' whose rhs begins with let!/use!/do! runs as a nested computation.
+ match tryRebindCeLetWithBangRhs ceenv isRec m trivia binds innerComp with
+ | Some rewritten ->
+ Some(TranslateComputationExpression ceenv CompExprTranslationPass.Initial q varSpace rewritten translatedCtxt)
+ | None ->
- match normalizedBindings with
- | NormalizedBinding(mBinding = mBinding) :: _ -> failAt mBinding
- | _ -> failAt m
+ // For 'query' check immediately
+ if ceenv.isQuery then
+ match (List.map (BindingNormalization.NormalizeBinding ValOrMemberBinding cenv ceenv.env) binds) with
+ | [ NormalizedBinding(_, SynBindingKind.Normal, false, false, _, _, _, _, _, _, _, _) ] when not isRec -> ()
+ | normalizedBindings ->
+ let failAt m =
+ error (Error(FSComp.SR.tcNonSimpleLetBindingInQuery (), m))
- // Add the variables to the query variable space, on demand
- let varSpace =
- addVarsToVarSpace varSpace (fun mQueryOp env ->
- // Normalize the bindings before detecting the bound variables
- match (List.map (BindingNormalization.NormalizeBinding ValOrMemberBinding cenv env) binds) with
- | [ NormalizedBinding(kind = SynBindingKind.Normal; shouldInline = false; isMutable = false; pat = pat) ] ->
- // successful case
- use _holder = TemporarilySuspendReportingTypecheckResultsToSink cenv.tcSink
+ match normalizedBindings with
+ | NormalizedBinding(mBinding = mBinding) :: _ -> failAt mBinding
+ | _ -> failAt m
- let _, _, vspecs, envinner, _ =
- TcMatchPattern cenv (NewInferenceType cenv.g) env ceenv.tpenv pat None TcTrueMatchClause.No
+ // Add the variables to the query variable space, on demand
+ let varSpace =
+ addVarsToVarSpace varSpace (fun mQueryOp env ->
+ // Normalize the bindings before detecting the bound variables
+ match (List.map (BindingNormalization.NormalizeBinding ValOrMemberBinding cenv env) binds) with
+ | [ NormalizedBinding(kind = SynBindingKind.Normal; shouldInline = false; isMutable = false; pat = pat) ] ->
+ // successful case
+ use _holder = TemporarilySuspendReportingTypecheckResultsToSink cenv.tcSink
- vspecs, envinner
- | _ ->
- // error case
- error (Error(FSComp.SR.tcCustomOperationMayNotBeUsedInConjunctionWithNonSimpleLetBindings (), mQueryOp)))
+ let _, _, vspecs, envinner, _ =
+ TcMatchPattern cenv (NewInferenceType cenv.g) env ceenv.tpenv pat None TcTrueMatchClause.No
- Some(
- TranslateComputationExpression ceenv CompExprTranslationPass.Initial q varSpace innerComp (fun holeFill ->
- translatedCtxt (
- SynExpr.LetOrUse
- {
- IsRecursive = isRec
- //isUse = false,
- IsFromSource = isFromSource
- //isBang = false,
- Bindings = binds
- Body = holeFill
- Range = m
- Trivia = trivia
- }
- ))
- )
+ vspecs, envinner
+ | _ ->
+ // error case
+ error (Error(FSComp.SR.tcCustomOperationMayNotBeUsedInConjunctionWithNonSimpleLetBindings (), mQueryOp)))
+
+ Some(
+ TranslateComputationExpression ceenv CompExprTranslationPass.Initial q varSpace innerComp (fun holeFill ->
+ translatedCtxt (
+ SynExpr.LetOrUse
+ {
+ IsRecursive = isRec
+ //isUse = false,
+ IsFromSource = isFromSource
+ //isBang = false,
+ Bindings = binds
+ Body = holeFill
+ Range = m
+ Trivia = trivia
+ }
+ ))
+ )
// 'use x = expr in expr'
| LetOrUse({
@@ -2528,24 +2589,12 @@ and ConsumeCustomOpClauses
let rebind =
if maintainsVarSpaceUsingBind then
let binding =
- SynBinding(
- accessibility = None,
- kind = SynBindingKind.Normal,
- isInline = false,
- isMutable = false,
- attributes = [],
- xmlDoc = PreXmlDoc.Empty,
- valData = SynInfo.emptySynValData,
- headPat = intoPat,
- returnInfo = None,
- expr = dataCompAfterOp,
- range = dataCompAfterOp.Range,
- debugPoint = DebugPointAtBinding.NoneAtLet,
- trivia =
- { SynBindingTrivia.Zero with
- LeadingKeyword = SynLeadingKeyword.LetBang intoPat.Range
- }
- )
+ mkSynLetBangBinding
+ intoPat.Range
+ intoPat
+ dataCompAfterOp
+ DebugPointAtBinding.NoneAtLet
+ dataCompAfterOp.Range
SynExpr.LetOrUse
{
@@ -2589,24 +2638,7 @@ and ConsumeCustomOpClauses
let rebind =
if lastUsesBind then
let binding =
- SynBinding(
- accessibility = None,
- kind = SynBindingKind.Normal,
- isInline = false,
- isMutable = false,
- attributes = [],
- xmlDoc = PreXmlDoc.Empty,
- valData = SynInfo.emptySynValData,
- headPat = varSpacePat,
- returnInfo = None,
- expr = dataCompPrior,
- range = dataCompPrior.Range,
- debugPoint = DebugPointAtBinding.NoneAtLet,
- trivia =
- { SynBindingTrivia.Zero with
- LeadingKeyword = SynLeadingKeyword.LetBang dataCompPrior.Range
- }
- )
+ mkSynLetBangBinding dataCompPrior.Range varSpacePat dataCompPrior DebugPointAtBinding.NoneAtLet dataCompPrior.Range
SynExpr.LetOrUse
{
@@ -2878,24 +2910,7 @@ and TranslateComputationExpression (ceenv: ComputationExpressionContext<'a>) fir
let letBangBind =
let binding =
- SynBinding(
- accessibility = None,
- kind = SynBindingKind.Normal,
- isInline = false,
- isMutable = false,
- attributes = [],
- xmlDoc = PreXmlDoc.Empty,
- valData = SynInfo.emptySynValData,
- headPat = SynPat.Const(SynConst.Unit, mUnit),
- returnInfo = None,
- expr = rhsExpr,
- range = rhsExpr.Range,
- debugPoint = DebugPointAtBinding.NoneAtDo,
- trivia =
- { SynBindingTrivia.Zero with
- LeadingKeyword = SynLeadingKeyword.LetBang m
- }
- )
+ mkSynLetBangBinding m (SynPat.Const(SynConst.Unit, mUnit)) rhsExpr DebugPointAtBinding.NoneAtDo rhsExpr.Range
SynExpr.LetOrUse
{
diff --git a/src/Compiler/Checking/Expressions/CheckExpressions.fs b/src/Compiler/Checking/Expressions/CheckExpressions.fs
index aba29d0aa86..e4b3e755841 100644
--- a/src/Compiler/Checking/Expressions/CheckExpressions.fs
+++ b/src/Compiler/Checking/Expressions/CheckExpressions.fs
@@ -6,7 +6,6 @@ module internal FSharp.Compiler.CheckExpressions
open System
open System.Collections.Generic
-open System.Text.RegularExpressions
open Internal.Utilities.Collections
open Internal.Utilities.Library
@@ -146,43 +145,6 @@ exception InvalidInternalsVisibleToAssemblyName of badName: string * fileName: s
exception InvalidAttributeTargetForLanguageElement of elementTargets: string array * allowedTargets: string array * range: range
-//----------------------------------------------------------------------------------------------
-// Helpers for determining if/what specifiers a string has.
-// Used to decide if interpolated string can be lowered to a concat call.
-// We don't care about single- vs multi-$ strings here, because lexer took care of that already.
-//----------------------------------------------------------------------------------------------
-[]
-let (|HasFormatSpecifier|_|) (s: string) =
- if
- Regex.IsMatch(
- s,
- // Regex pattern for something like: %[flags][width][.precision][type]
- """
- (^|[^%]) # Start with beginning of string or any char other than '%'
- (%%)*% # followed by an odd number of '%' chars
- [+-0 ]{0,3} # optionally followed by flags
- (\d+)? # optionally followed by width
- (\.\d+)? # optionally followed by .precision
- [bscdiuxXoBeEfFgGMOAat] # and then a char that determines specifier's type
- """,
- RegexOptions.Compiled ||| RegexOptions.IgnorePatternWhitespace)
- then
- ValueSome HasFormatSpecifier
- else
- ValueNone
-
-// Removes trailing "%s" unless it was escaped by another '%' (checks for odd sequence of '%' before final "%s")
-let (|WithTrailingStringSpecifierRemoved|) (s: string) =
- if s.EndsWith "%s" then
- let i = s.AsSpan(0, s.Length - 2).LastIndexOfAnyExcept '%'
- let diff = s.Length - 2 - i
- if diff &&& 1 <> 0 then
- s[..s.Length - 3]
- else
- s
- else
- s
-
/// Compute the available access rights from a particular location in code
let ComputeAccessRights eAccessPath eInternalsVisibleCompPaths eFamilyType =
AccessibleFrom (eAccessPath :: eInternalsVisibleCompPaths, eFamilyType)
@@ -659,31 +621,6 @@ let UnifyTupleTypeAndInferCharacteristics contextInfo (cenv: cenv) denv m knownT
AddCxTypeEqualsType contextInfo denv cenv.css m knownTy ty2
tupInfo, ptys
-// Allow inference of assembly-affinity and structness from the known type - even from another assembly. This is a rule of
-// the language design and allows effective cross-assembly use of anonymous types in some limited circumstances.
-let UnifyAnonRecdTypeAndInferCharacteristics contextInfo (cenv: cenv) denv m ty isExplicitStruct unsortedNames =
- let g = cenv.g
- let anonInfo, ptys =
- match tryDestAnonRecdTy g ty with
- | ValueSome (anonInfo, ptys) ->
- // Note: use the assembly of the known type, not the current assembly
- // Note: use the structness of the known type, unless explicit
- // Note: use the names of our type, since they are always explicit
- let tupInfo = (if isExplicitStruct then tupInfoStruct else anonInfo.TupInfo)
- let anonInfo = AnonRecdTypeInfo.Create(anonInfo.Assembly, tupInfo, unsortedNames)
- let ptys =
- if List.length ptys = Array.length unsortedNames then ptys
- else NewInferenceTypes g (Array.toList anonInfo.SortedNames)
- anonInfo, ptys
- | ValueNone ->
- // Note: no known anonymous record type - use our assembly
- let anonInfo = AnonRecdTypeInfo.Create(cenv.thisCcu, mkTupInfo isExplicitStruct, unsortedNames)
- anonInfo, NewInferenceTypes g (Array.toList anonInfo.SortedNames)
- let ty2 = TType_anon (anonInfo, ptys)
- AddCxTypeEqualsType contextInfo denv cenv.css m ty ty2
- anonInfo, ptys
-
-
/// Optimized unification routine that avoids creating new inference
/// variables unnecessarily
let UnifyFunctionTypeUndoIfFailed (cenv: cenv) denv m ty =
@@ -2000,24 +1937,23 @@ let CheckRecdExprDuplicateFields (elems: Ident list) =
//-------------------------------------------------------------------------
/// Helper used to check record expressions and record patterns
-let BuildFieldMap (cenv: cenv) env isPartial ty (flds: ((Ident list * Ident) * 'T) list) m =
+let BuildFieldMap (cenv: cenv) env isPartial ty (flds: (Ident * ExplicitOrSpread) list) m =
let g = cenv.g
let ad = env.eAccessRights
- let allFields = flds |> List.map (fun ((_, ident), _) -> ident)
- if allFields.Length > 1 then
- // In the case of nested record fields on the same level in record copy-and-update.
- // We need to reverse the list to get the correct order of fields.
- let idents = if isPartial then allFields |> List.rev else allFields
- CheckRecdExprDuplicateFields idents
+ let allFields = flds |> List.map (fun (ident, _) -> ident)
let fldResolutions =
flds
- |> List.choose (fun (fld, fldExpr) ->
+ |> List.choose (fun (fldId, fld) ->
try
- let fldPath, fldId = fld
- let frefSet = ResolveField cenv.tcSink cenv.nameResolver env.eNameResEnv ad ty fldPath fldId allFields
- Some(fld, frefSet, fldExpr)
+ let fldExpr, fldInfo =
+ match fld with
+ | ExplicitOrSpread.Explicit (path, fldExpr) -> ExplicitOrSpread.Explicit fldExpr, ExplicitOrSpread.Explicit (path, fldId)
+ | ExplicitOrSpread.Spread fldExpr -> ExplicitOrSpread.Spread fldExpr, ExplicitOrSpread.Spread fldId
+
+ ResolveField cenv.tcSink cenv.nameResolver env.eNameResEnv ad ty fldInfo allFields
+ |> Option.map (fun frefSet -> fldId, frefSet, fldExpr)
with e ->
errorRecoveryNoRange e
None
@@ -2051,7 +1987,7 @@ let BuildFieldMap (cenv: cenv) env isPartial ty (flds: ((Ident list * Ident) * '
rfinfo1.TypeInst, rfinfo1.TyconRef
let fldsmap, rfldsList =
- ((Map.empty, []), fldResolutions) ||> List.fold (fun (fs, rfldsList) ((_, ident), frefs, fldExpr) ->
+ ((Map.empty, []), fldResolutions) ||> List.fold (fun (fs, rfldsList) (ident, frefs, fldExpr) ->
match frefs |> List.filter (fun (FieldResolution(rfinfo2, _)) -> tyconRefEq g tcref rfinfo2.TyconRef) with
| [FieldResolution(rfinfo2, showDeprecated)] ->
@@ -3359,6 +3295,46 @@ let GetMethodArgs arg =
unnamedCallerArgs, namedCallerArgs
+let NotNullIfNotNullParamNames g (minfo: MethInfo) =
+ match minfo with
+ | ILMeth(ilMethInfo = ilminfo) when ilminfo.RawMetadata.Return.CustomAttrsStored.HasWellKnownAttribute (g, WellKnownILAttributes.NotNullIfNotNullAttribute) ->
+ ilminfo.RawMetadata.Return.CustomAttrs.AsArray()
+ |> Array.toList
+ |> List.choose (fun attr ->
+ if classifyILAttrib attr &&& WellKnownILAttributes.NotNullIfNotNullAttribute <> WellKnownILAttributes.None then
+ match decodeILAttribData attr with
+ | [ ILAttribElem.String (Some paramName) ], _ -> Some paramName
+ | _ -> None
+ else
+ None)
+ | FSMeth(valRef = vref) ->
+ match vref.ValReprInfo with
+ | Some (ValReprInfo(result = retInfo)) when ArgReprInfoHasWellKnownAttribute g WellKnownValAttributes.NotNullIfNotNullAttribute retInfo ->
+ retInfo.Attribs.AsList()
+ |> List.choose (fun attrib ->
+ if classifyValAttrib g attrib &&& WellKnownValAttributes.NotNullIfNotNullAttribute <> WellKnownValAttributes.None then
+ match attrib with
+ | Attrib(unnamedArgs = [ AttribStringArg paramName ]) -> Some paramName
+ | _ -> None
+ else
+ None)
+ | _ -> []
+ | _ -> []
+
+// Resolve the caller argument bound to 'paramName' and return the type of its type-checked expression.
+let TryGetCallerArgType g (minfo: MethInfo) (callerArgs: CallerArgs<_>) paramName =
+ // First try to find a named argument with the given name
+ callerArgs.Named
+ |> List.tryPick (List.tryPick (fun (CallerNamedArg(id, arg)) -> if id.idText = paramName then Some arg else None))
+ |> Option.orElseWith (fun () ->
+ // If there is no matching named argument, find the argument in the same position as the parameter with the given name
+ minfo.GetParamNames()
+ |> Seq.concat
+ |> Seq.tryFindIndex (fun nm -> match nm with Some nm -> nm = paramName | _ -> false)
+ |> Option.bind (fun idx -> Seq.concat callerArgs.Unnamed |> Seq.tryItem idx)
+ )
+ |> Option.map (fun arg -> tyOfExpr g arg.Expr)
+
//-------------------------------------------------------------------------
// Helpers dealing with sequence expressions
//-------------------------------------------------------------------------
@@ -6055,11 +6031,33 @@ and TcExprUndelayed (cenv: cenv) (overallTy: OverallTy) env tpenv (synExpr: SynE
| SynExpr.AnonRecd (isStruct, withExprOpt, unsortedFieldExprs, mWholeExpr, trivia) ->
match withExprOpt with
- | None | IsSimpleOrBoundExpr ->
- TcNonControlFlowExpr env <| fun env ->
- TcPossiblyPropagatingExprLeafThenConvert (fun ty -> isAnonRecdTy g ty || isTyparTy g ty) cenv overallTy env mWholeExpr (fun overallTy ->
- TcAnonRecdExpr cenv overallTy env tpenv (isStruct, withExprOpt, unsortedFieldExprs, mWholeExpr)
- )
+ | None | Some (IsSimpleOrBoundExpr, _) ->
+ let anySpreadsNotSimpleOrBound =
+ unsortedFieldExprs
+ |> List.exists (function
+ | SynExprAnonRecordFieldOrSpread.Field _
+ | SynExprAnonRecordFieldOrSpread.Spread (SynExprSpread (expr = IsSimpleOrBoundExpr), _) -> false
+ | SynExprAnonRecordFieldOrSpread.Spread _ -> true)
+
+ if anySpreadsNotSimpleOrBound then
+ let rec loop unsortedFieldExprs cont =
+ match unsortedFieldExprs with
+ | [] -> cont []
+ | (SynExprAnonRecordFieldOrSpread.Field _ as fieldOrSpread) :: unsortedFieldExprs
+ | (SynExprAnonRecordFieldOrSpread.Spread (SynExprSpread (expr = IsSimpleOrBoundExpr), _) as fieldOrSpread) :: unsortedFieldExprs ->
+ loop unsortedFieldExprs (cont << fun fields -> fieldOrSpread :: fields)
+ | SynExprAnonRecordFieldOrSpread.Spread (SynExprSpread (spreadRange, spreadExpr, m), maybeBlockSep) :: unsortedFieldExprs ->
+ bindSrcIn spreadExpr (fun spreadExpr ->
+ loop unsortedFieldExprs (cont << fun fields ->
+ SynExprAnonRecordFieldOrSpread.Spread (SynExprSpread (spreadRange, spreadExpr, m), maybeBlockSep) :: fields))
+
+ let wrappedExpr = loop unsortedFieldExprs (fun synRecdFields -> SynExpr.AnonRecd (isStruct, withExprOpt, synRecdFields, mWholeExpr, trivia))
+ TcExpr cenv overallTy env tpenv wrappedExpr
+ else
+ TcNonControlFlowExpr env <| fun env ->
+ TcPossiblyPropagatingExprLeafThenConvert (fun ty -> isAnonRecdTy g ty || isTyparTy g ty) cenv overallTy env mWholeExpr (fun overallTy ->
+ TcAnonRecdExpr cenv overallTy env tpenv (isStruct, withExprOpt, unsortedFieldExprs, mWholeExpr)
+ )
| Some withExpr ->
BindOriginalRecdExpr withExpr (fun withExpr -> SynExpr.AnonRecd (isStruct, withExpr, unsortedFieldExprs, mWholeExpr, trivia))
|> TcExpr cenv overallTy env tpenv
@@ -6094,9 +6092,31 @@ and TcExprUndelayed (cenv: cenv) (overallTy: OverallTy) env tpenv (synExpr: SynE
| SynExpr.Record (inherits, withExprOpt, synRecdFields, mWholeExpr) ->
match withExprOpt with
- | None | IsSimpleOrBoundExpr ->
- TcNonControlFlowExpr env <| fun env ->
- TcExprRecord cenv overallTy env tpenv (inherits, withExprOpt, synRecdFields, mWholeExpr)
+ | None | Some (IsSimpleOrBoundExpr, _) ->
+ let anySpreadsNotSimpleOrBound =
+ synRecdFields
+ |> List.exists (function
+ | SynExprRecordFieldOrSpread.Field _
+ | SynExprRecordFieldOrSpread.Spread (SynExprSpread (expr = IsSimpleOrBoundExpr), _) -> false
+ | SynExprRecordFieldOrSpread.Spread _ -> true)
+
+ if anySpreadsNotSimpleOrBound then
+ let rec loop synRecdFields cont =
+ match synRecdFields with
+ | [] -> cont []
+ | (SynExprRecordFieldOrSpread.Field _ as fieldOrSpread) :: synRecdFields
+ | (SynExprRecordFieldOrSpread.Spread (SynExprSpread (expr = IsSimpleOrBoundExpr), _) as fieldOrSpread) :: synRecdFields ->
+ loop synRecdFields (cont << fun fields -> fieldOrSpread :: fields)
+ | SynExprRecordFieldOrSpread.Spread (SynExprSpread (spreadRange, spreadExpr, m), maybeBlockSep) :: synRecdFields ->
+ bindSrcIn spreadExpr (fun spreadExpr ->
+ loop synRecdFields (cont << fun fields ->
+ SynExprRecordFieldOrSpread.Spread (SynExprSpread (spreadRange, spreadExpr, m), maybeBlockSep) :: fields))
+
+ let wrappedExpr = loop synRecdFields (fun synRecdFields -> SynExpr.Record (inherits, withExprOpt, synRecdFields, mWholeExpr))
+ TcExpr cenv overallTy env tpenv wrappedExpr
+ else
+ TcNonControlFlowExpr env <| fun env ->
+ TcExprRecord cenv overallTy env tpenv (inherits, withExprOpt, synRecdFields, mWholeExpr)
| Some withExpr ->
BindOriginalRecdExpr withExpr (fun withExpr -> SynExpr.Record (inherits, withExpr, synRecdFields, mWholeExpr))
|> TcExpr cenv overallTy env tpenv
@@ -6451,6 +6471,13 @@ and TcExprRecord (cenv: cenv) overallTy env tpenv (inherits, withExprOpt, synRec
let g = cenv.g
CallExprHasTypeSink cenv.tcSink (mWholeExpr, env.NameEnv, overallTy.Commit, env.AccessRights)
let requiresCtor = (GetCtorShapeCounter env = 1) // Get special expression forms for constructors
+
+ if requiresCtor then
+ for fieldOrSpread in synRecdFields do
+ match fieldOrSpread with
+ | SynExprRecordFieldOrSpread.Spread (SynExprSpread (spreadRange = m), _) -> errorR (Error (FSComp.SR.parsSpreadNotSupported (), m))
+ | SynExprRecordFieldOrSpread.Field _ -> ()
+
let haveCtor = Option.isSome inherits
TcPossiblyPropagatingExprLeafThenConvert (fun ty -> requiresCtor || haveCtor || isRecdTy g ty || isTyparTy g ty) cenv overallTy env mWholeExpr (fun overallTy ->
TcRecdExpr cenv overallTy env tpenv (inherits, withExprOpt, synRecdFields, mWholeExpr)
@@ -7059,7 +7086,7 @@ and TcCtorCall isNaked cenv env tpenv (overallTy: OverallTy) objTy mObjTyOpt ite
error(Error(FSComp.SR.tcSyntaxCanOnlyBeUsedToCreateObjectTypes(if superInit then "inherit" else "new"), mWholeCall))
// Check a record construction expression
-and TcRecordConstruction (cenv: cenv) (overallTy: TType) isObjExpr env tpenv withExprInfoOpt objTy fldsList m =
+and TcRecordConstruction (cenv: cenv) (overallTy: TType) isObjExpr env tpenv withExprInfoOpt (spreadSrcs : (Expr -> Expr) list) objTy fldsList m =
let g = cenv.g
let tcref, tinst = destAppTy g objTy
@@ -7072,24 +7099,44 @@ and TcRecordConstruction (cenv: cenv) (overallTy: TType) isObjExpr env tpenv wit
errorR(Error(FSComp.SR.tcConstructorRequiresCall(tycon.DisplayName), m))
let fspecs = tycon.TrueInstanceFieldsAsList
- // Freshen types and work out their subtype flexibility
- let fldsList =
- [ for fname, fexpr in fldsList do
- let fspec =
- try
- fspecs |> List.find (fun fspec -> fspec.LogicalName = fname)
- with :? KeyNotFoundException ->
- error (Error(FSComp.SR.tcUndefinedField(fname, NicePrint.minimalStringOfType env.DisplayEnv objTy), m))
- let fty = actualTyOfRecdFieldForTycon tycon tinst fspec
- let flex = not (isTyparTy g fty)
- yield (fname, fexpr, fty, flex) ]
+ // Freshen types and work out their subtype flexibility
// Type check and generalize the supplied bindings
let fldsList, tpenv =
let env = { env with eContextInfo = ContextInfo.RecordFields }
- (tpenv, fldsList) ||> List.mapFold (fun tpenv (fname, fexpr, fty, flex) ->
- let fieldExpr, tpenv = TcExprFlex cenv flex false fty env tpenv fexpr
- (fname, fieldExpr), tpenv)
+ let rec tcFields checkedFields tpenv fields =
+ match fields with
+ | [] -> List.rev checkedFields, tpenv
+ | (fname, ExplicitOrSpread.Explicit fexpr) :: fields ->
+ let checkedFields, tpenv =
+ fspecs
+ |> List.tryFind (fun fspec -> fspec.LogicalName = fname)
+ |> Option.map (fun fspec ->
+ let fty = actualTyOfRecdFieldForTycon tycon tinst fspec
+ let flex = not (isTyparTy g fty)
+ let fieldExpr, tpenv = TcExprFlex cenv flex false fty env tpenv fexpr
+ (fname, fieldExpr) :: checkedFields, tpenv)
+ |> Option.defaultWith (fun () ->
+ error (Error(FSComp.SR.tcUndefinedField(fname, NicePrint.minimalStringOfType env.DisplayEnv objTy), m)))
+
+ tcFields checkedFields tpenv fields
+
+ | (fname, ExplicitOrSpread.Spread (ty, spreadValue)) :: fields ->
+ let checkedFields =
+ fspecs
+ |> List.tryPick (fun fspec ->
+ if fspec.LogicalName = fname then
+ let fty = actualTyOfRecdFieldForTycon tycon tinst fspec
+ let overallTy = MustConvertTo (false, fty)
+ UnifyOverallType cenv env m overallTy ty
+ let fieldExpr = TcAdjustExprForTypeDirectedConversions cenv overallTy ty env m spreadValue
+ Some ((fname, mkCoerceIfNeeded g fty (tyOfExpr g fieldExpr) fieldExpr) :: checkedFields)
+ else None)
+ |> Option.defaultValue checkedFields // We ignore extra fields from spreads.
+
+ tcFields checkedFields tpenv fields
+
+ tcFields [] tpenv fldsList
// Add rebindings for unbound field when an "old value" is available
// Effect order: mutable fields may get modified by other bindings...
@@ -7149,16 +7196,20 @@ and TcRecordConstruction (cenv: cenv) (overallTy: TType) isObjExpr env tpenv wit
let expr = mkRecordExpr g (GetRecdInfo env, tcref, tinst, rfrefs, args, m)
let expr =
- match withExprInfoOpt with
- | None ->
- // '{ recd fields }'. //
- expr
+ let locals =
+ [
+ match withExprInfoOpt with
+ | None -> id
+ | Some (withExpr, withExprAddrVal, _) ->
+ // '{ recd with fields }'.
+ // Assign the first object to a tmp and then construct
+ let wrap, oldaddr, _readonly, _writeonly = mkExprAddrOfExpr g tycon.IsStructOrEnumTycon false NeverMutates withExpr None m
+ fun expr -> wrap (mkCompGenLet m withExprAddrVal oldaddr expr)
- | Some (withExpr, withExprAddrVal, _) ->
- // '{ recd with fields }'.
- // Assign the first object to a tmp and then construct
- let wrap, oldaddr, _readonly, _writeonly = mkExprAddrOfExpr g tycon.IsStructOrEnumTycon false NeverMutates withExpr None m
- wrap (mkCompGenLet m withExprAddrVal oldaddr expr)
+ yield! spreadSrcs
+ ]
+
+ (locals, expr) ||> List.foldBack (fun local expr -> local expr)
expr, tpenv
@@ -7450,10 +7501,11 @@ and TcObjectExpr (cenv: cenv) env tpenv (objTy, realObjTy, argopt, binds, extraI
let fldsList =
binds |> List.map (fun b ->
match BindingNormalization.NormalizeBinding ObjExprBinding cenv env b with
- | NormalizedBinding (_, _, _, _, [], _, _, _, SynPat.Named(SynIdent(id,_), _, _, _), NormalizedBindingRhs(_, _, rhsExpr), _, _) -> id.idText, rhsExpr
+ | NormalizedBinding (_, _, _, _, [], _, _, _, SynPat.Named(SynIdent(id,_), _, _, _), NormalizedBindingRhs(_, _, rhsExpr), _, _) -> id.idText, ExplicitOrSpread.Explicit rhsExpr
| _ -> error(Error(FSComp.SR.tcOnlySimpleBindingsCanBeUsedInConstructionExpressions(), b.RangeOfBindingWithoutRhs)))
- TcRecordConstruction cenv objTy true env tpenv None objTy fldsList mWholeExpr
+ let spreadSrcs = []
+ TcRecordConstruction cenv objTy true env tpenv None spreadSrcs objTy fldsList mWholeExpr
else
// object expression construction e.g. { new A() with ... } or { new IA with ... }
let ctorCall, baseIdOpt, tpenv =
@@ -7634,6 +7686,96 @@ and TcFormatStringExpr cenv (overallTy: OverallTy) env m tpenv (fmtString: strin
mkString g m fmtString, tpenv
)
+/// Lower a string-typed interpolated string to a reflection-free System.String.Concat of its parts,
+/// type-checking each part in place.
+and TcInterpolatedStringViaConcat (cenv: cenv, overallTy: OverallTy, env: TcEnv, m: range, tpenv: UnscopedTyparEnv, parts: SynInterpolatedStringPart list) =
+ let g = cenv.g
+ let mSynth = m.MakeSynthetic()
+ let strLit (s: string) = SynExpr.Const(SynConst.String(s, SynStringKind.Regular, mSynth), mSynth)
+ let paren (e: SynExpr) = SynExpr.Paren(e, range0, None, mSynth)
+
+ // '(sprintf spec e : string)': format a printf-specifier hole (still reflection-based).
+ let sprintfOp (spec: string, e: SynExpr) =
+ let f = mkSynApp1 (mkSynLidGet mSynth [ "Microsoft"; "FSharp"; "Core"; "ExtraTopLevelOperators" ] "sprintf") (strLit spec) mSynth
+ let call = mkSynApp1 f (paren e) mSynth
+ SynExpr.Typed(call, SynType.LongIdent(SynLongIdent([ mkSynId mSynth "string" ], [], [ None ])), mSynth)
+
+ // 'String.Format(InvariantCulture, "{0,align:format}", e)': format an aligned or '{e:fmt}' hole.
+ let stringFormatOp (alignment: SynExpr option, format: Ident option, e: SynExpr) =
+ let alignText = match alignment with Some (SynExpr.Const (SynConst.Int32 n, _)) -> "," + string n | _ -> ""
+ let formatText = match format with Some n -> ":" + n.idText | None -> ""
+ let netFormat = "{0" + alignText + formatText + "}"
+ let invariant = mkSynLidGet mSynth [ "System"; "Globalization"; "CultureInfo" ] "InvariantCulture"
+ let args = paren (SynExpr.Tuple(false, [ invariant; strLit netFormat; e ], [ range0; range0 ], mSynth))
+ mkSynApp1 (mkSynLidGet mSynth [ "System"; "String" ] "Format") args mSynth
+
+ // Type-check one hole and convert it to a (string expression, may-be-null) pair.
+ let convertHole (synFill: SynExpr, formatting: SynInterpolationFormatting, tpenv: UnscopedTyparEnv) =
+ // Constrain the hole to 'constraintTy', then render it with 'string' as for a plain '{x}' hole. Used for
+ // bare specifiers (no flags/width/precision) that act only as a type annotation: the value renders the
+ // same through 'string' as through the specifier. ('%u' is not one of these: it reinterprets a signed
+ // value as unsigned, so it does not match 'string' - e.g. '%u' of -1 is "4294967295".)
+ let convertViaString constraintTy =
+ let fill, tpenv = TcExpr cenv (MustEqual constraintTy) env tpenv synFill
+ (mkCallStringOperator g m (tyOfExpr g fill) fill, false), tpenv
+ match formatting with
+ | SynInterpolationFormatting.Printf (spec, _) ->
+ match spec with
+ // A bare '%s' requires a string; pass it through (it may be null) instead of formatting via 'sprintf'.
+ // Its type is the one 'sprintf "%s"' uses, so a nullable string is accepted here too.
+ | "%s" ->
+ let fill, tpenv = TcExpr cenv (MustEqual (CheckFormatStrings.stringFormatTy g)) env tpenv synFill
+ (fill, true), tpenv
+ | "%c" -> convertViaString g.char_ty
+ | "%d" | "%i" -> convertViaString (CheckFormatStrings.mkFlexibleIntFormatTypar g m)
+ | "%M" -> convertViaString (CheckFormatStrings.mkFlexibleDecimalFormatTypar g m)
+ | _ ->
+ let arg, tpenv = TcExpr cenv (MustEqual g.string_ty) env tpenv (sprintfOp (spec, synFill))
+ (arg, false), tpenv
+ | SynInterpolationFormatting.DotNet (alignment, format) ->
+ // Type-checking the hole here is also where a function value gets warned about.
+ let fill, tpenv = TcExprFlex2 cenv (NewInferenceType g) env false tpenv synFill
+ let fillTy = tyOfExpr g fill
+ if g.langVersion.SupportsFeature LanguageFeature.WarnWhenFunctionValueUsedAsInterpolatedStringArg && (isFunTy g fillTy || isDelegateTy g fillTy) then
+ warning (Error(FSComp.SR.tcFunctionValueUsedAsInterpolatedStringArg (), synFill.Range))
+ match alignment, format with
+ | None, None -> (if isStringTy g fillTy then (fill, true) else (mkCallStringOperator g m fillTy fill, false)), tpenv
+ | _ ->
+ // Format the already-checked hole via a synthesized 'String.Format', binding its boxed value
+ // to a temporary so the hole is not type-checked a second time. Re-checking 'synFill' would
+ // duplicate any error in it; boxing to 'obj' keeps the 'Format' overload unambiguous (so a
+ // hole that already failed to check doesn't also leak a confusing 'Format' overload error).
+ let boxedFill = mkCallBox g m fillTy fill
+ let tmpVal, _ = mkLocal mSynth "interpHole" (tyOfExpr g boxedFill)
+ let envInner = AddLocalVal g cenv.tcSink mSynth tmpVal env
+ let tmpRef = SynExpr.Ident(mkSynId mSynth tmpVal.LogicalName)
+ let arg, tpenv = TcExpr cenv (MustEqual g.string_ty) envInner tpenv (stringFormatOp (alignment, format, tmpRef))
+ (mkCompGenLet mSynth tmpVal boxedFill arg, false), tpenv
+
+ // One (string expression, may-be-null) per non-empty part; a builder (not map) since 'tpenv' threads
+ // through the holes. Literals and conversions are never null; only a raw string passthrough may be.
+ let argExprs, tpenv =
+ let ra = ResizeArray()
+ let mutable tpenvAcc = tpenv
+ for part in parts do
+ match part with
+ | SynInterpolatedStringPart.String (s, _) ->
+ if s <> "" then
+ ra.Add((mkString g m (s.Replace("%%", "%")), false))
+ | SynInterpolatedStringPart.FillExpr (synFill, formatting) ->
+ let argExpr, tpenvAfter = convertHole (synFill, formatting, tpenvAcc)
+ ra.Add argExpr
+ tpenvAcc <- tpenvAfter
+ List.ofSeq ra, tpenvAcc
+
+ let resultExpr =
+ match argExprs with
+ // A lone arg has no Concat to map its null to ""; a possibly-null one coalesces via 'string'.
+ | [ (single, true) ] -> mkCallStringOperator g m g.string_ty single
+ | _ -> mkStringConcat (g, m, List.map fst argExprs)
+
+ TcPropagatingExprLeafThenConvert cenv overallTy g.string_ty env m (fun () -> resultExpr, tpenv)
+
/// Check an interpolated string expression
and [] warnForFunctionValuesInFillExprs (g: TcGlobals) argTys synFillExprs =
match argTys, synFillExprs with
@@ -7651,11 +7793,7 @@ and TcInterpolatedStringExpr cenv (overallTy: OverallTy) env m tpenv (parts: Syn
parts
|> List.choose (function
| SynInterpolatedStringPart.String _ -> None
- | SynInterpolatedStringPart.FillExpr (fillExpr, _) ->
- match fillExpr with
- // Detect "x" part of "...{x,3}..."
- | SynExpr.Tuple (false, [e; SynExpr.Const (SynConst.Int32 _align, _)], _, _) -> Some e
- | e -> Some e)
+ | SynInterpolatedStringPart.FillExpr (fillExpr, _) -> Some fillExpr)
let stringFragmentRanges =
parts
@@ -7723,19 +7861,21 @@ and TcInterpolatedStringExpr cenv (overallTy: OverallTy) env m tpenv (parts: Syn
let isFormattableString = (match stringKind with Choice2Of2 _ -> true | _ -> false)
- // The format string used for checking in CheckFormatStrings. This replaces interpolation holes with %P
+ // The format string used for checking in CheckFormatStrings, reconstructed from the parts: each
+ // hole becomes a '%P(...)' marker, prefixed by its printf specifier or alignment.
let printfFormatString =
parts
|> List.map (function
| SynInterpolatedStringPart.String (s, _) -> s
- | SynInterpolatedStringPart.FillExpr (fillExpr, format) ->
+ | SynInterpolatedStringPart.FillExpr (_, SynInterpolationFormatting.Printf (spec, _)) ->
+ spec + "%P()"
+ | SynInterpolatedStringPart.FillExpr (fillExpr, SynInterpolationFormatting.DotNet (alignment, format)) ->
+ match fillExpr with
+ | SynExpr.Tuple (false, _, _, _) -> errorR(Error(FSComp.SR.tcInvalidAlignmentInInterpolatedString(), m))
+ | _ -> ()
let alignText =
- match fillExpr with
- // Validate and detect ",3" part of "...{x,3}..."
- | SynExpr.Tuple (false, args, _, _) ->
- match args with
- | [_; SynExpr.Const (SynConst.Int32 align, _)] -> string align
- | _ -> errorR(Error(FSComp.SR.tcInvalidAlignmentInInterpolatedString(), m)); ""
+ match alignment with
+ | Some (SynExpr.Const (SynConst.Int32 align, _)) -> string align
| _ -> ""
let formatText = match format with None -> "()" | Some n -> "(" + n.idText + ")"
"%" + alignText + "P" + formatText )
@@ -7789,75 +7929,28 @@ and TcInterpolatedStringExpr cenv (overallTy: OverallTy) env m tpenv (parts: Syn
else
let str = mkString g m printfFormatString
mkCallNewFormat g m printerTy printerArgTy printerResidueTy printerResultTy printerTupleTy str, tpenv
+ elif isString then
+ // String-typed interpolation: lower to a reflection-free System.String.Concat of the parts,
+ // type-checking each hole in place (no separate batch, no flat fill-expression list).
+ TcInterpolatedStringViaConcat (cenv, overallTy, env, m, tpenv, parts)
else
- // Type check the expressions filling the holes
+ // $"...{x}..." used as a PrintfFormat value: build a PrintfFormat that captures the args.
let fillExprs, tpenv = TcExprsNoFlexes cenv env m tpenv argTys synFillExprs
if g.langVersion.SupportsFeature LanguageFeature.WarnWhenFunctionValueUsedAsInterpolatedStringArg then
warnForFunctionValuesInFillExprs g argTys synFillExprs
- // Take all interpolated string parts and typed fill expressions
- // and convert them to typed expressions that can be used as args to System.String.Concat
- // return an empty list if there are some format specifiers that make lowering to not applicable
- let rec concatenable acc fillExprs parts =
- match fillExprs, parts with
- | [], [] ->
- List.rev acc
- | [], SynInterpolatedStringPart.FillExpr _ :: _
- | _, [] ->
- // This should never happen, there will always be as many typed fill expressions
- // as there are FillExprs in the interpolated string parts
- error(InternalError("Mismatch in interpolation expression count", m))
- | _, SynInterpolatedStringPart.String (WithTrailingStringSpecifierRemoved "", _) :: parts ->
- // If the string is empty (after trimming %s of the end), we skip it
- concatenable acc fillExprs parts
-
- | _, SynInterpolatedStringPart.String (WithTrailingStringSpecifierRemoved HasFormatSpecifier, _) :: _
- | _, SynInterpolatedStringPart.FillExpr (_, Some _) :: _
- | _, SynInterpolatedStringPart.FillExpr (SynExpr.Tuple (isStruct = false; exprs = [_; SynExpr.Const (SynConst.Int32 _, _)]), _) :: _ ->
- // There was a format specifier like %20s{..} or {..,20} or {x:hh}, which means we cannot simply concat
- []
-
- | _, SynInterpolatedStringPart.String (s & WithTrailingStringSpecifierRemoved trimmed, m) :: parts ->
- let finalStr = trimmed.Replace("%%", "%")
- concatenable (mkString g (shiftEnd 0 (finalStr.Length - s.Length) m) finalStr :: acc) fillExprs parts
-
- | fillExpr :: fillExprs, SynInterpolatedStringPart.FillExpr _ :: parts ->
- concatenable (fillExpr :: acc) fillExprs parts
-
- let canLower =
- g.langVersion.SupportsFeature LanguageFeature.LowerInterpolatedStringToConcat
- && isString
- && argTys |> List.forall (isStringTy g)
-
- let concatenableExprs = if canLower then concatenable [] fillExprs parts else []
-
- match concatenableExprs with
- | [p1; p2; p3; p4] -> TcPropagatingExprLeafThenConvert cenv overallTy g.string_ty env m (fun () -> mkStaticCall_String_Concat4 g m p1 p2 p3 p4, tpenv)
- | [p1; p2; p3] -> TcPropagatingExprLeafThenConvert cenv overallTy g.string_ty env m (fun () -> mkStaticCall_String_Concat3 g m p1 p2 p3, tpenv)
- | [p1; p2] -> TcPropagatingExprLeafThenConvert cenv overallTy g.string_ty env m (fun () -> mkStaticCall_String_Concat2 g m p1 p2, tpenv)
- | [p1] -> p1, tpenv
- | _ ->
+ let fillExprsBoxed = (argTys, fillExprs) ||> List.map2 (mkCallBox g m)
- let fillExprsBoxed = (argTys, fillExprs) ||> List.map2 (mkCallBox g m)
-
- let argsExpr = mkArray (g.obj_ty_withNulls, fillExprsBoxed, m)
- let percentATysExpr =
- if percentATys.Length = 0 then
- mkNull m (mkArrayType g g.system_Type_ty)
- else
- let tyExprs = percentATys |> Array.map (mkCallTypeOf g m) |> Array.toList
- mkArray (g.system_Type_ty, tyExprs, m)
-
- let fmtExpr = MakeMethInfoCall cenv.amap m newFormatMethod [] [mkString g m printfFormatString; argsExpr; percentATysExpr] None
-
- if isString then
- TcPropagatingExprLeafThenConvert cenv overallTy g.string_ty env (* true *) m (fun () ->
- // Make the call to sprintf
- mkCall_sprintf g m printerTy fmtExpr [], tpenv
- )
+ let argsExpr = mkArray (g.obj_ty_withNulls, fillExprsBoxed, m)
+ let percentATysExpr =
+ if percentATys.Length = 0 then
+ mkNull m (mkArrayType g g.system_Type_ty)
else
- fmtExpr, tpenv
+ let tyExprs = percentATys |> Array.map (mkCallTypeOf g m) |> Array.toList
+ mkArray (g.system_Type_ty, tyExprs, m)
+
+ MakeMethInfoCall cenv.amap m newFormatMethod [] [mkString g m printfFormatString; argsExpr; percentATysExpr] None, tpenv
// The case for $"..." used as type FormattableString or IFormattable
| Choice2Of2 createFormattableStringMethod ->
@@ -7965,6 +8058,7 @@ and TcAssertExpr cenv overallTy env (m: range) tpenv x =
and TcRecdExpr cenv overallTy env tpenv (inherits, withExprOpt, synRecdFields, mWholeExpr) =
CallExprHasTypeSink cenv.tcSink (mWholeExpr, env.NameEnv, overallTy, env.eAccessRights)
let g = cenv.g
+ let ad = env.eAccessRights
let requiresCtor = (GetCtorShapeCounter env = 1) // Get special expression forms for constructors
let haveCtor = Option.isSome inherits
@@ -7981,27 +8075,24 @@ and TcRecdExpr cenv overallTy env tpenv (inherits, withExprOpt, synRecdFields, m
let hasOrigExpr = withExprOptChecked.IsSome
- let fldsList =
- let flds =
- synRecdFields
- |> List.map (fun (SynExprRecordField (fieldName = (synLongId, isOk); expr = exprBeingAssigned)) ->
- // if we met at least one field that is not syntactically correct - raise ReportedError to transfer control to the recovery routine
- if not isOk then
- // raising ReportedError None transfers control to the closest errorRecovery point but do not make any records into log
- // we assume that parse errors were already reported
- raise (ReportedError None)
-
- match withExprOpt, synLongId.LongIdent, exprBeingAssigned with
- | _, [ id ], _ -> ([], id), exprBeingAssigned
- | Some withExpr, lid, Some exprBeingAssigned -> TransformAstForNestedUpdates cenv env overallTy lid exprBeingAssigned withExpr
- | _ -> List.frontAndBack synLongId.LongIdent, exprBeingAssigned)
-
- let flds = if hasOrigExpr then GroupUpdatesToNestedFields flds else flds
+ let spreadSrcs, fldsList, tpenv =
+ let spreadSrcTys, spreadSrcs, flds =
+ Spreads.Values.Records.check
+ TcExprFlex
+ g
+ env
+ cenv
+ tpenv
+ ad
+ mWholeExpr
+ withExprOpt
+ overallTy
+ synRecdFields
+
// Check if the overall type is an anon record type and if so raise an copy-update syntax error
// let f (r: {| A: int; C: int |}) = { r with A = 1; B = 2; C = 3 }
if isAnonRecdTy cenv.g overallTy || isStructAnonRecdTy cenv.g overallTy then
- for fld, _ in flds do
- let _, fldId = fld
+ for fldId, _ in flds do
match TryFindAnonRecdFieldOfType g overallTy fldId.idText with
| Some item ->
CallNameResolutionSink cenv.tcSink (fldId.idRange, env.eNameResEnv, item, emptyTyparInst, ItemOccurrence.UseInType, env.eAccessRights)
@@ -8012,30 +8103,42 @@ and TcRecdExpr cenv overallTy env tpenv (inherits, withExprOpt, synRecdFields, m
// Use the right } in the expression
let lastPartRange = withStartEnd (mkPos mWholeExpr.StartLine (mWholeExpr.EndColumn - 1)) (mkPos mWholeExpr.StartLine mWholeExpr.EndColumn) mWholeExpr
errorR(Error(FSComp.SR.chkCopyUpdateSyntaxInAnonRecords(), lastPartRange))
- []
+ [], [], tpenv
else
// If the overall type is a record type build a map of the fields
- match flds with
- | [] -> []
- | _ ->
- match BuildFieldMap cenv env hasOrigExpr overallTy flds mWholeExpr with
- | None -> []
- | Some(tinst, tcref, _, fldsList) ->
+ let fieldMap =
+ match flds with
+ | [] -> []
+ | _ ->
+ let tcrefs =
+ spreadSrcTys
+ |> List.choose (tryTcrefOfAppTy g >> ValueOption.toOption)
+
+ let env = { env with eNameResEnv = (env.eNameResEnv, tcrefs) ||> AddTyconRefsToNameEnv BulkAdd.Yes false g cenv.amap ad mWholeExpr false }
- let gtyp = mkWoNullAppTy tcref tinst
- UnifyTypes cenv env mWholeExpr overallTy gtyp
+ match BuildFieldMap cenv env hasOrigExpr overallTy flds mWholeExpr with
+ | None -> []
+ | Some(tinst, tcref, _, fldsList) ->
+
+ let gtyp = mkWoNullAppTy tcref tinst
+ UnifyTypes cenv env mWholeExpr overallTy gtyp
- // (#15290) For copy-and-update expressions, register the record type as a related symbol
- // so that "Find All References" on the record type includes copy-and-update usages.
- // Reported via CallRelatedSymbolSink to avoid affecting colorization or symbol info.
- if hasOrigExpr then
- let item = Item.Types(tcref.DisplayName, [gtyp])
- CallRelatedSymbolSink cenv.tcSink (mWholeExpr, item, RelatedSymbolUseKind.CopyAndUpdateRecord)
+ // (#15290) For copy-and-update expressions, register the record type as a related symbol
+ // so that "Find All References" on the record type includes copy-and-update usages.
+ // Reported via CallRelatedSymbolSink to avoid affecting colorization or symbol info.
+ if hasOrigExpr then
+ let item = Item.Types(tcref.DisplayName, [gtyp])
+ CallRelatedSymbolSink cenv.tcSink (mWholeExpr, item, RelatedSymbolUseKind.CopyAndUpdateRecord)
- [ for n, v in fldsList do
- match v with
- | Some v -> yield n, v
- | None -> () ]
+ [
+ for fldId, fld in fldsList do
+ match fld with
+ | ExplicitOrSpread.Explicit None -> ()
+ | ExplicitOrSpread.Explicit (Some fieldExpr) -> fldId, ExplicitOrSpread.Explicit fieldExpr
+ | ExplicitOrSpread.Spread spread -> fldId, ExplicitOrSpread.Spread spread
+ ]
+
+ spreadSrcs, fieldMap, tpenv
let withExprInfoOpt =
match withExprOptChecked with
@@ -8081,7 +8184,7 @@ and TcRecdExpr cenv overallTy env tpenv (inherits, withExprOpt, synRecdFields, m
SolveTypeAsError env.DisplayEnv cenv.css mWholeExpr overallTy
mkDefault (mWholeExpr, overallTy), tpenv
else
- let expr, tpenv = TcRecordConstruction cenv overallTy false env tpenv withExprInfoOpt overallTy fldsList mWholeExpr
+ let expr, tpenv = TcRecordConstruction cenv overallTy false env tpenv withExprInfoOpt spreadSrcs overallTy fldsList mWholeExpr
let expr =
match superInitExprOpt with
@@ -8090,12 +8193,6 @@ and TcRecdExpr cenv overallTy env tpenv (inherits, withExprOpt, synRecdFields, m
| None -> expr
expr, tpenv
-and CheckAnonRecdExprDuplicateFields (elems: Ident array) =
- elems |> Array.iteri (fun i (uc1: Ident) ->
- elems |> Array.iteri (fun j (uc2: Ident) ->
- if j > i && uc1.idText = uc2.idText then
- errorR(Error (FSComp.SR.tcAnonRecdDuplicateFieldId(uc1.idText), uc1.idRange))))
-
// Check '{| .... |}'
and TcAnonRecdExpr cenv (overallTy: TType) env tpenv (isStruct, optOrigSynExpr, unsortedFieldIdsAndSynExprsGiven, mWholeExpr) =
match optOrigSynExpr with
@@ -8106,7 +8203,10 @@ and TcAnonRecdExpr cenv (overallTy: TType) env tpenv (isStruct, optOrigSynExpr,
// Ideally we should also check for duplicate field IDs in the TcCopyAndUpdateAnonRecdExpr case, but currently the logic is too complex to guarantee a proper error reporting
// So here we error instead errorR to avoid cascading internal errors
unsortedFieldIdsAndSynExprsGiven
- |> List.countBy (fun (fId, _, _) -> textOfLid fId.LongIdent)
+ |> List.choose (function
+ | SynExprAnonRecordFieldOrSpread.Field (SynExprAnonRecordField (fieldName = SynLongIdent (name, _, _)), _) -> Some name
+ | SynExprAnonRecordFieldOrSpread.Spread _ -> (* Spreads are allowed to shadow fields. *) None)
+ |> List.countBy textOfLid
|> List.iter (fun (label, count) ->
if count > 1 then error (Error (FSComp.SR.tcAnonRecdDuplicateFieldId(label), mWholeExpr)))
@@ -8115,39 +8215,74 @@ and TcAnonRecdExpr cenv (overallTy: TType) env tpenv (isStruct, optOrigSynExpr,
and TcNewAnonRecdExpr cenv (overallTy: TType) env tpenv (isStruct, unsortedFieldIdsAndSynExprsGiven, mWholeExpr) =
let g = cenv.g
- let unsortedFieldSynExprsGiven = unsortedFieldIdsAndSynExprsGiven |> List.map (fun (_, _, fieldExpr) -> fieldExpr)
- let unsortedFieldIds = unsortedFieldIdsAndSynExprsGiven |> List.map (fun (synLongIdent, _, _) -> synLongIdent.LongIdent[0]) |> List.toArray
- let anonInfo, sortedFieldTys = UnifyAnonRecdTypeAndInferCharacteristics env.eContextInfo cenv env.DisplayEnv mWholeExpr overallTy isStruct unsortedFieldIds
-
- if unsortedFieldIds.Length > 1 then
- CheckAnonRecdExprDuplicateFields unsortedFieldIds
-
- // Sort into canonical order
- let sortedIndexedArgs =
- unsortedFieldIdsAndSynExprsGiven
- |> List.indexed
- |> List.sortBy (fun (i,_) -> unsortedFieldIds[i].idText)
-
- // Map from sorted indexes to unsorted indexes
- let sigma = sortedIndexedArgs |> List.map fst |> List.toArray
- let sortedFieldExprs = sortedIndexedArgs |> List.map snd
-
- sortedFieldExprs |> List.iteri (fun j (synLongIdent, _, _) ->
- let m = rangeOfLid synLongIdent.LongIdent
- let item = Item.AnonRecdField(anonInfo, sortedFieldTys, j, m)
- CallNameResolutionSink cenv.tcSink (m, env.NameEnv, item, emptyTyparInst, ItemOccurrence.Use, env.eAccessRights))
-
- let unsortedFieldTys =
- sortedFieldTys
- |> List.indexed
- |> List.sortBy (fun (sortedIdx, _) -> sigma[sortedIdx])
- |> List.map snd
+ let ad = env.eAccessRights
- let flexes = unsortedFieldTys |> List.map (fun _ -> true)
+ let maybeAnonRecdTargetTy = tryDestAnonRecdTy g overallTy
+
+ let spreadSrcs, unsortedFields, anonInfo, tpenv =
+ let spreadSrcs, fieldIdsInAlphabeticalOrder, fieldTysInAlphabeticalOrder, fieldsInSrcOrder =
+ Spreads.Values.AnonymousRecords.check
+ TcExprFlex
+ TcAdjustExprForTypeDirectedConversions
+ MustConvertTo
+ UnifyOverallType
+ ignore
+ g
+ env
+ cenv
+ tpenv
+ ad
+ mWholeExpr
+ maybeAnonRecdTargetTy
+ None
+ overallTy
+ unsortedFieldIdsAndSynExprsGiven
+
+ // Unify the overall ty with the inferred target anonymous record type.
+ let anonInfo, sortedFieldTys =
+ let anonInfo, sortedFieldTys =
+ let unsortedNames =
+ fieldsInSrcOrder
+ |> List.map (fun (fieldId, _, _) -> fieldId)
+ |> List.toArray
+
+ match maybeAnonRecdTargetTy with
+ | ValueSome (anonInfo, _) ->
+ // Note: use the assembly of the known type, not the current assembly
+ // Note: use the structness of the known type, unless explicit
+ // Note: use the names of our type, since they are always explicit
+ let tupInfo = if isStruct then tupInfoStruct else anonInfo.TupInfo
+ let anonInfo = AnonRecdTypeInfo.Create(anonInfo.Assembly, tupInfo, unsortedNames)
+ anonInfo, fieldTysInAlphabeticalOrder
+ | ValueNone ->
+ // Note: no known anonymous record type - use our assembly
+ let anonInfo = AnonRecdTypeInfo.Create(cenv.thisCcu, mkTupInfo isStruct, unsortedNames)
+ anonInfo, fieldTysInAlphabeticalOrder
+ let ty2 = TType_anon (anonInfo, sortedFieldTys)
+ AddCxTypeEqualsType env.eContextInfo env.DisplayEnv cenv.css mWholeExpr overallTy ty2
+ anonInfo, sortedFieldTys
+
+ // All sorted field identifiers, including potential duplicates.
+ let sortedNames = fieldIdsInAlphabeticalOrder
+
+ // Call name resolution.
+ sortedNames
+ |> List.iteri (fun j fieldName ->
+ let m = fieldName.idRange
+ let item = Item.AnonRecdField(anonInfo, sortedFieldTys, j, m)
+ CallNameResolutionSink cenv.tcSink (m, env.NameEnv, item, emptyTyparInst, ItemOccurrence.Use, env.eAccessRights))
+
+ spreadSrcs, fieldsInSrcOrder, anonInfo, tpenv
+
+ let unsortedNames = [| for fieldName, _, _ in unsortedFields -> fieldName |]
+ let unsortedTys = [ for _, fieldTy, _ in unsortedFields -> fieldTy ]
+ let unsortedExprs = [ for _, _, tcField in unsortedFields -> tcField () ]
- let unsortedCheckedArgs, tpenv = TcExprsWithFlexes cenv env mWholeExpr tpenv flexes unsortedFieldTys unsortedFieldSynExprsGiven
+ let expr =
+ (spreadSrcs, mkAnonRecd g mWholeExpr anonInfo unsortedNames unsortedExprs unsortedTys)
+ ||> List.foldBack (fun wrap expr -> wrap expr)
- mkAnonRecd g mWholeExpr anonInfo unsortedFieldIds unsortedCheckedArgs unsortedFieldTys, tpenv
+ expr, tpenv
and TcCopyAndUpdateAnonRecdExpr cenv (overallTy: TType) env tpenv (isStruct, (origExpr, blockSeparator), unsortedFieldIdsAndSynExprsGiven, mWholeExpr) =
// The fairly complex case '{| origExpr with X = 1; Y = 2 |}'
@@ -8160,6 +8295,7 @@ and TcCopyAndUpdateAnonRecdExpr cenv (overallTy: TType) env tpenv (isStruct, (or
// Unlike in the case of record type copy-and-update {| a with X = 1 |} does not force a.X to exist or have had type 'int'
let g = cenv.g
+ let ad = env.eAccessRights
let origExprTy = NewInferenceType g
let origExprChecked, tpenv = TcExpr cenv (MustEqual origExprTy) env tpenv origExpr
let oldv, oldve = mkCompGenLocal mWholeExpr "inputRecord" origExprTy
@@ -8168,17 +8304,27 @@ and TcCopyAndUpdateAnonRecdExpr cenv (overallTy: TType) env tpenv (isStruct, (or
if not (isAppTy g origExprTy || isAnonRecdTy g origExprTy) then
error (Error (FSComp.SR.tcCopyAndUpdateNeedsRecordType(), mOrigExpr))
- // Expand expressions with respect to potential nesting
- let unsortedFieldIdsAndSynExprsGiven =
- unsortedFieldIdsAndSynExprsGiven
- |> List.map (fun (synLongIdent, _, exprBeingAssigned) ->
- match synLongIdent.LongIdent with
- | [] -> error(Error(FSComp.SR.nrUnexpectedEmptyLongId(), mWholeExpr))
- | [ id ] -> ([], id), Some exprBeingAssigned
- | lid -> TransformAstForNestedUpdates cenv env origExprTy lid exprBeingAssigned (origExpr, blockSeparator))
- |> GroupUpdatesToNestedFields
-
- let unsortedFieldSynExprsGiven = unsortedFieldIdsAndSynExprsGiven |> List.choose snd
+ let maybeAnonRecdTargetTy = tryDestAnonRecdTy g overallTy
+
+ // Collect explicitly-defined fields and fields from spreads
+ // and expand expressions with respect to potential nesting.
+ let spreadSrcs, _fieldIdsInAlphabeticalOrder, _fieldTysInAlphabeticalOrder, fieldsInSrcOrder =
+ Spreads.Values.AnonymousRecords.check
+ TcExprFlex
+ TcAdjustExprForTypeDirectedConversions
+ MustConvertTo
+ UnifyOverallType
+ (fun m -> errorR (Error (FSComp.SR.tcRecordExprSpreadWithCannotBeUsedWithSpreads (), m)))
+ g
+ env
+ cenv
+ tpenv
+ ad
+ mWholeExpr
+ maybeAnonRecdTargetTy
+ (Some (origExpr, blockSeparator))
+ origExprTy
+ unsortedFieldIdsAndSynExprsGiven
let origExprIsStruct =
match tryDestAnonRecdTy g origExprTy with
@@ -8195,37 +8341,59 @@ and TcCopyAndUpdateAnonRecdExpr cenv (overallTy: TType) env tpenv (isStruct, (or
/// - Choice2Of2 for a binding coming from the original expression
let unsortedIdAndExprsAll =
[|
- for (_, id), e in unsortedFieldIdsAndSynExprsGiven do
- yield (id, Choice1Of2 e)
+ for id, ty, tcField in fieldsInSrcOrder do
+ yield (id, ty, Choice1Of2 tcField)
+
match tryDestAnonRecdTy g origExprTy with
| ValueSome (anonInfo, tinst) ->
for i, id in Array.indexed anonInfo.SortedIds do
- yield id, Choice2Of2 (mkAnonRecdFieldGetViaExprAddr (anonInfo, oldveaddr, tinst, i, mOrigExpr))
+ yield id, NewInferenceType g, Choice2Of2 (mkAnonRecdFieldGetViaExprAddr (anonInfo, oldveaddr, tinst, i, mOrigExpr))
| ValueNone ->
match tryAppTy g origExprTy with
| ValueSome(tcref, tinst) when tcref.IsRecordTycon ->
let fspecs = tcref.Deref.TrueInstanceFieldsAsList
for fspec in fspecs do
- yield fspec.Id, Choice2Of2 (mkRecdFieldGetViaExprAddr (oldveaddr, tcref.MakeNestedRecdFieldRef fspec, tinst, mOrigExpr))
+ yield fspec.Id, NewInferenceType g, Choice2Of2 (mkRecdFieldGetViaExprAddr (oldveaddr, tcref.MakeNestedRecdFieldRef fspec, tinst, mOrigExpr))
| _ ->
error (Error (FSComp.SR.tcCopyAndUpdateNeedsRecordType(), mOrigExpr))
|]
- |> Array.distinctBy (fst >> textOfId)
+ |> Array.distinctBy (fun (fieldId, _, _) -> textOfId fieldId)
- let unsortedFieldIdsAll = Array.map fst unsortedIdAndExprsAll
+ let unsortedFieldIdsAll = [|for fieldId, _, _ in unsortedIdAndExprsAll -> fieldId|]
- let anonInfo, sortedFieldTysAll = UnifyAnonRecdTypeAndInferCharacteristics env.eContextInfo cenv env.DisplayEnv mWholeExpr overallTy isStruct unsortedFieldIdsAll
-
- let sortedIndexedFieldsAll = unsortedIdAndExprsAll |> Array.indexed |> Array.sortBy (snd >> fst >> textOfId)
+ let sortedIndexedFieldsAll = unsortedIdAndExprsAll |> Array.indexed |> Array.sortBy (fun (_, (fieldId, _, _)) -> textOfId fieldId)
// map from sorted indexes to unsorted indexes
let sigma = Array.map fst sortedIndexedFieldsAll
let sortedFieldsAll = Array.map snd sortedIndexedFieldsAll
+ // Unify the overall ty with the inferred target anonymous record type.
+ let anonInfo, sortedFieldTysAll =
+ let anonInfo =
+ let unsortedNames = unsortedFieldIdsAll
+
+ match maybeAnonRecdTargetTy with
+ | ValueSome (anonInfo, _) ->
+ // Note: use the assembly of the known type, not the current assembly
+ // Note: use the structness of the known type, unless explicit
+ // Note: use the names of our type, since they are always explicit
+ let tupInfo = if isStruct then tupInfoStruct else anonInfo.TupInfo
+ let anonInfo = AnonRecdTypeInfo.Create(anonInfo.Assembly, tupInfo, unsortedNames)
+ anonInfo
+ | ValueNone ->
+ // Note: no known anonymous record type - use our assembly
+ let anonInfo = AnonRecdTypeInfo.Create(cenv.thisCcu, mkTupInfo isStruct, unsortedNames)
+ anonInfo
+
+ let sortedFieldTysAll = [for _, ty, _ in sortedFieldsAll -> ty]
+ let ty2 = TType_anon (anonInfo, sortedFieldTysAll)
+ AddCxTypeEqualsType env.eContextInfo env.DisplayEnv cenv.css mWholeExpr overallTy ty2
+ anonInfo, sortedFieldTysAll
+
// Report _all_ identifiers to name resolution. We should likely just report the ones
// that are explicit in source code.
- sortedFieldsAll |> Array.iteri (fun j (fieldId, expr) ->
+ sortedFieldsAll |> Array.iteri (fun j (fieldId, _, expr) ->
match expr with
| Choice1Of2 _ ->
let item = Item.AnonRecdField(anonInfo, sortedFieldTysAll, j, fieldId.idRange)
@@ -8238,33 +8406,21 @@ and TcCopyAndUpdateAnonRecdExpr cenv (overallTy: TType) env tpenv (isStruct, (or
|> List.sortBy (fun (sortedIdx, _) -> sigma[sortedIdx])
|> List.map snd
- let unsortedFieldTysGiven =
- unsortedFieldTysAll
- |> List.take unsortedFieldIdsAndSynExprsGiven.Length
-
- let flexes = unsortedFieldTysGiven |> List.map (fun _ -> true)
-
// Check the expressions in unsorted order
- let unsortedFieldExprsGiven, tpenv =
- TcExprsWithFlexes cenv env mWholeExpr tpenv flexes unsortedFieldTysGiven unsortedFieldSynExprsGiven
-
- let unsortedFieldExprsGiven = unsortedFieldExprsGiven |> List.toArray
-
- let unsortedFieldIds =
- unsortedIdAndExprsAll
- |> Array.map fst
+ let unsortedFieldExprsGiven = fieldsInSrcOrder |> List.map (fun (_, _, tcField) -> tcField ()) |> List.toArray
+ let unsortedFieldIds = unsortedFieldIdsAll
let unsortedFieldExprs =
unsortedIdAndExprsAll
- |> Array.mapi (fun unsortedIdx (_, expr) ->
+ |> Array.mapi (fun unsortedIdx (_fieldId, ty, expr) ->
match expr with
| Choice1Of2 _ -> unsortedFieldExprsGiven[unsortedIdx]
- | Choice2Of2 subExpr -> UnifyTypes cenv env mOrigExpr (tyOfExpr g subExpr) unsortedFieldTysAll[unsortedIdx]; subExpr)
+ | Choice2Of2 subExpr -> UnifyTypes cenv env mOrigExpr (tyOfExpr g subExpr) ty; subExpr)
|> List.ofArray
// Permute the expressions to sorted order in the TAST
let expr = mkAnonRecd g mWholeExpr anonInfo unsortedFieldIds unsortedFieldExprs unsortedFieldTysAll
- let expr = wrap expr
+ let expr = (wrap :: spreadSrcs, expr) ||> List.foldBack (fun wrap expr -> wrap expr)
// Bind the original expression
let expr = mkCompGenLet mOrigExpr oldv origExprChecked expr
@@ -8834,6 +8990,13 @@ and TcApplicationThen (cenv: cenv) (overallTy: OverallTy) env tpenv mExprAndArg
| [] when g.langVersion.SupportsFeature LanguageFeature.EmptyBodiedComputationExpressions -> Some (EmptyFieldListAsUnit (SynExpr.Const (SynConst.Unit, range0)))
| _ -> None
+ let (|SpreadsOnly|_|) recordFields =
+ if g.langVersion.SupportsFeature LanguageFeature.RecordSpreads && not (List.isEmpty recordFields) && recordFields |> List.forall (function SynExprRecordFieldOrSpread.Spread _ -> true | _ -> false) then
+ let spreadRanges = recordFields |> List.choose (function SynExprRecordFieldOrSpread.Spread (SynExprSpread (spreadRange = m), _) -> Some m | _ -> None)
+ Some (SpreadsOnly spreadRanges)
+ else
+ None
+
// If the type of 'synArg' unifies as a function type, then this is a function application, otherwise
// it is an error or a computation expression or indexer or delegate invoke
match UnifyFunctionTypeUndoIfFailed cenv denv mLeftExpr exprTy with
@@ -8854,15 +9017,21 @@ and TcApplicationThen (cenv: cenv) (overallTy: OverallTy) env tpenv mExprAndArg
// Note that 'seq' predated computation expressions and is not actually a computation expression builder
// though users don't realise that.
let synArg =
- match synArg with
+ match leftExpr with
// seq { comp }
// seq { }
- | SynExpr.ComputationExpr (false, comp, m)
- | SynExpr.Record (None, None, EmptyFieldListAsUnit comp, m) when
- (match leftExpr with
- | ApplicableExpr(expr=Expr.Op(TOp.Coerce, _, [SeqExpr g], _)) -> true
- | _ -> false) ->
- SynExpr.ComputationExpr (true, comp, m)
+ | ApplicableExpr(expr=Expr.Op(TOp.Coerce, _, [SeqExpr g], _)) ->
+ match synArg with
+ | SynExpr.ComputationExpr (false, comp, m)
+ | SynExpr.Record (None, None, EmptyFieldListAsUnit comp, m) ->
+ SynExpr.ComputationExpr (true, comp, m)
+
+ | SynExpr.Record (None, None, SpreadsOnly spreadRanges, m) ->
+ for m in spreadRanges do
+ errorR (Error (FSComp.SR.parsSpreadNotSupported (), m))
+ SynExpr.ComputationExpr (true, arbExpr ("spreadsInSeqExpr", m), m)
+
+ | _ -> synArg
| _ -> synArg
@@ -9446,7 +9615,9 @@ and TcImplicitOpItemThen (cenv: cenv) overallTy env id sln tpenv mItem delayed =
| SynExpr.Tuple (_, synExprs, _, _)
| SynExpr.ArrayOrList (_, synExprs, _) -> synExprs |> List.forall isSimpleArgument
- | SynExpr.Record (copyInfo=copyOpt; recordFields=fields) -> copyOpt |> Option.forall (fst >> isSimpleArgument) && fields |> List.forall ((fun (SynExprRecordField(expr=e)) -> e) >> Option.forall isSimpleArgument)
+ | SynExpr.Record (copyInfo=copyOpt; recordFields=fields) ->
+ copyOpt |> Option.forall (fst >> isSimpleArgument)
+ && fields |> List.forall ((function SynExprRecordFieldOrSpread.Field (SynExprRecordField(expr=e), _) -> e | _ -> None) >> Option.forall isSimpleArgument)
| SynExpr.App (_, _, synExpr, synExpr2, _) -> isSimpleArgument synExpr && isSimpleArgument synExpr2
| SynExpr.IfThenElse (ifExpr=synExpr; thenExpr=synExpr2; elseExpr=synExprOpt) -> isSimpleArgument synExpr && isSimpleArgument synExpr2 && Option.forall isSimpleArgument synExprOpt
| SynExpr.DotIndexedGet (synExpr, _, _, _) -> isSimpleArgument synExpr
@@ -10307,12 +10478,26 @@ and TcMethodApplication_UniqueOverloadInference
let arityFilteredCandidates = candidateMethsAndProps
- let makeOneCalledMeth (minfo, pinfoOpt, usesParamArrayConversion) =
+ let makeOneCalledMeth (minfo: MethInfo, pinfoOpt, usesParamArrayConversion) =
let minst = FreshenMethInfo mItem minfo
let callerTyArgs =
match tyArgsOpt with
| Some tyargs -> minfo.AdjustUserTypeInstForFSharpStyleIndexedExtensionMembers tyargs
| None -> minst
+
+ // If the return value is [], give the return a fresh nullness inference variable here so that
+ // unique-overload inference does not prematurely commit the result to the declared (nullable) nullness. The real
+ // nullness is resolved post argument type-checking (see below), once the argument types are known.
+ let minfo =
+ if not minfo.IsConstructor && g.checkNullness && g.langVersion.SupportsFeature LanguageFeature.NotNullIfNotNull then
+ match NotNullIfNotNullParamNames g minfo with
+ | [ _ ] ->
+ let retTy = minfo.GetFSharpReturnType(cenv.amap, mMethExpr, callerTyArgs)
+ MethInfoWithModifiedReturnType(minfo, replaceNullnessOfTy (NewNullnessVar()) retTy)
+ | _ -> minfo
+ else
+ minfo
+
CalledMeth(cenv.infoReader, Some(env.NameEnv), isCheckingAttributeCall, FreshenMethInfo, mMethExpr, ad, minfo, minst, callerTyArgs, pinfoOpt, callerObjArgTys, callerArgs, usesParamArrayConversion, true, objTyOpt, staticTyOpt)
let preArgumentTypeCheckingCalledMethGroup =
@@ -10570,6 +10755,29 @@ and TcMethodApplication
match tyArgsOpt with
| Some tyargs -> minfo.AdjustUserTypeInstForFSharpStyleIndexedExtensionMembers tyargs
| None -> minst
+
+ let minfo =
+ if not minfo.IsConstructor && g.checkNullness && g.langVersion.SupportsFeature LanguageFeature.NotNullIfNotNull then
+ // 'minfo' may already carry a placeholder return nullness from unique-overload inference (phase 1);
+ // strip it back to the base method before applying the real (argument-derived) nullness.
+ let baseMinfo = match minfo with MethInfoWithModifiedReturnType(inner, _) -> inner | _ -> minfo
+ match NotNullIfNotNullParamNames g baseMinfo with
+ | [ paramName ] ->
+ match TryGetCallerArgType g baseMinfo callerArgs paramName with
+ | Some callerArgTy ->
+ let callerArgTy = if isByrefTy g callerArgTy then destByrefTy g callerArgTy else callerArgTy
+ let retTy = baseMinfo.GetFSharpReturnType(cenv.amap, mMethExpr, callerTyArgs)
+ let argNullness =
+ if TypeNullIsTrueValue g callerArgTy || TypeNullIsExtraValueNew g mMethExpr callerArgTy then
+ g.knownWithNull
+ else
+ nullnessOfTy g callerArgTy
+ MethInfoWithModifiedReturnType(baseMinfo, replaceNullnessOfTy argNullness retTy)
+ | None -> baseMinfo
+ | _ -> baseMinfo
+ else
+ minfo
+
CalledMeth(cenv.infoReader, Some(env.NameEnv), isCheckingAttributeCall, FreshenMethInfo, mMethExpr, ad, minfo, minst, callerTyArgs, pinfoOpt, callerObjArgTys, callerArgs, usesParamArrayConversion, true, objTyOpt, staticTyOpt))
// Commit unassociated constraints prior to member overload resolution where there is ambiguity
@@ -11745,6 +11953,14 @@ and TcAttributeEx canFail (cenv: cenv) (env: TcEnv) attrTgt attrEx (synAttr: Syn
let tcref = tcrefOfAppTy g ty
+ if not tcref.Typars.IsEmpty then
+ match canFail with
+ | TcCanFail.IgnoreAllErrors | TcCanFail.IgnoreMemberResoutionError -> [], true
+ | TcCanFail.ReportAllErrors ->
+ errorR(Error(FSComp.SR.tcGenericAttributesNotSupported(tcref.DisplayName), mAttr))
+ [], false
+ else
+
let conditionalCallDefineOpt = TryFindTyconRefStringAttribute g mAttr g.attrib_ConditionalAttribute tcref
match conditionalCallDefineOpt, cenv.conditionalDefines with
diff --git a/src/Compiler/Checking/Expressions/CheckExpressions.fsi b/src/Compiler/Checking/Expressions/CheckExpressions.fsi
index 4fc6a1dfde7..199ce0e720e 100644
--- a/src/Compiler/Checking/Expressions/CheckExpressions.fsi
+++ b/src/Compiler/Checking/Expressions/CheckExpressions.fsi
@@ -907,15 +907,21 @@ val UnifyTupleTypeAndInferCharacteristics:
'T list ->
TupInfo * TTypes
+/// Helper used to check for duplicate fields in records.
+val CheckRecdExprDuplicateFields: elems: Ident list -> unit
+
/// Helper used to check both record expressions and record patterns
val BuildFieldMap:
cenv: TcFileState ->
env: TcEnv ->
isPartial: bool ->
ty: TType ->
- flds: ((Ident list * Ident) * 'T) list ->
+ flds: (Ident * ExplicitOrSpread) list ->
m: range ->
- (TypeInst * TyconRef * Map * (string * 'T) list) option
+ (TypeInst *
+ TyconRef *
+ Map> *
+ (string * ExplicitOrSpread<'Explicit, 'Spread>) list) option
/// Check a long identifier 'Case' or 'Case argsR' that has been resolved to an active pattern case
val TcPatLongIdentActivePatternCase:
diff --git a/src/Compiler/Checking/MethodCalls.fs b/src/Compiler/Checking/MethodCalls.fs
index 156e52faee1..adf79f17a67 100644
--- a/src/Compiler/Checking/MethodCalls.fs
+++ b/src/Compiler/Checking/MethodCalls.fs
@@ -1250,6 +1250,14 @@ let rec BuildMethodCall tcVal g amap isMutable m isProp minfo valUseFlags minst
let expr = mkCoerceExpr (expr, retTy, m, exprTy)
expr, retTy
+ | MethInfoWithModifiedReturnType((FSMeth(_, _, vref, _) as innerMeth), retTy) ->
+ // Build the inner call directly, without re-invoking TakeObjAddrForMethodCall.
+ let vExpr, vExprTy = tcVal vref valUseFlags (innerMeth.DeclaringTypeInst @ minst) m
+ let expr, exprTy = BuildFSharpMethodApp g m vref vExpr vExprTy allArgs
+
+ let expr = mkCoerceExpr (expr, retTy, m, exprTy)
+ expr, retTy
+
| MethInfoWithModifiedReturnType _ ->
failwith "MethInfoWithModifiedReturnType: unexpected inner method kind"
diff --git a/src/Compiler/Checking/NameResolution.fs b/src/Compiler/Checking/NameResolution.fs
index 9be3d04e58f..ffb206076f6 100644
--- a/src/Compiler/Checking/NameResolution.fs
+++ b/src/Compiler/Checking/NameResolution.fs
@@ -4011,17 +4011,30 @@ let SuggestLabelsOfRelatedRecords g (nenv: NameResolutionEnv) (id: Ident) (allFi
UndefinedName(0, FSComp.SR.undefinedNameRecordLabel, id, suggestLabels)
+[]
+type internal ExplicitOrSpread<'Explicit, 'Spread> =
+ /// An expression or value derived from an explicit member or record field.
+ | Explicit of 'Explicit
+
+ /// An expression or value derived from a member or field coming from a spread.
+ | Spread of 'Spread
+
+let (|ExplicitOrSpread|) (ExplicitOrSpread.Explicit value | ExplicitOrSpread.Spread value) = value
+
/// Resolve a long identifier representing a record field
-let ResolveFieldPrim sink (ncenv: NameResolver) nenv ad ty (mp, id: Ident) allFields =
+let ResolveFieldPrim sink (ncenv: NameResolver) nenv ad ty (fldInfo: ExplicitOrSpread<'Explicit * Ident, Ident>) allFields =
+ let m = match fldInfo with ExplicitOrSpread.Explicit (_, id) | ExplicitOrSpread.Spread id -> id.idRange
let typeNameResInfo = TypeNameResolutionInfo.Default
let g = ncenv.g
- let m = id.idRange
- match mp with
- | [] ->
+
+ match fldInfo with
+ | ExplicitOrSpread.Explicit ([], id)
+ | ExplicitOrSpread.Spread id ->
let lookup() =
let frefs =
- try Map.find id.idText nenv.eFieldLabels
- with :? KeyNotFoundException ->
+ match Map.tryFind id.idText nenv.eFieldLabels with
+ | Some frefs -> frefs
+ | None ->
// record label is unknown -> suggest related labels and give a hint to the user
error(SuggestLabelsOfRelatedRecords g nenv id allFields)
@@ -4038,9 +4051,10 @@ let ResolveFieldPrim sink (ncenv: NameResolver) nenv ad ty (mp, id: Ident) allFi
match tryTcrefOfAppTy g ty with
| ValueSome tcref ->
match ncenv.InfoReader.TryFindRecdOrClassFieldInfoOfType(id.idText, m, ty) with
- | ValueSome (RecdFieldInfo(_, rfref)) -> [ResolutionInfo.Empty, FieldResolution(FreshenRecdFieldRef ncenv m rfref, false)]
+ | ValueSome (RecdFieldInfo(_, rfref)) -> Some [ResolutionInfo.Empty, FieldResolution(FreshenRecdFieldRef ncenv m rfref, false)]
| _ ->
- if tcref.IsRecordTycon then
+ if fldInfo.IsSpread then None
+ elif tcref.IsRecordTycon then
// record label doesn't belong to record type -> suggest other labels of same record
let suggestLabels (addToBuffer: string -> unit) =
for label in SuggestOtherLabelsOfSameRecordType g nenv ty id allFields do
@@ -4050,9 +4064,9 @@ let ResolveFieldPrim sink (ncenv: NameResolver) nenv ad ty (mp, id: Ident) allFi
let errorText = FSComp.SR.nrRecordDoesNotContainSuchLabel(typeName, id.idText)
error(ErrorWithSuggestions(errorText, m, id.idText, suggestLabels))
else
- lookup()
- | ValueNone -> lookup()
- | _ ->
+ Some (lookup())
+ | ValueNone -> Some (lookup())
+ | ExplicitOrSpread.Explicit (mp, id) ->
let lid = (mp@[id])
let tyconSearch ad () =
match lid with
@@ -4082,17 +4096,18 @@ let ResolveFieldPrim sink (ncenv: NameResolver) nenv ad ty (mp, id: Ident) allFi
if not (isNil rest) then
errorR(Error(FSComp.SR.nrInvalidFieldLabel(), (List.head rest).idRange))
- [(resInfo, item)]
+ Some [(resInfo, item)]
-let ResolveField sink ncenv nenv ad ty mp id allFields =
- let res = ResolveFieldPrim sink ncenv nenv ad ty (mp, id) allFields
+let ResolveField sink ncenv nenv ad ty fldInfo allFields =
+ let res = ResolveFieldPrim sink ncenv nenv ad ty fldInfo allFields
// Register the results of any field paths "Module.Type" in "Module.Type.field" as a name resolution. (Note, the path resolution
// info is only non-empty if there was a unique resolution of the field)
- let checker = ResultTyparChecker(fun () -> true)
res
- |> List.map (fun (resInfo, rfref) ->
- ResolutionInfo.SendEntityPathToSink(sink, ncenv, nenv, ItemOccurrence.UseInType, ad, resInfo, checker)
- rfref)
+ |> Option.map (fun res ->
+ let checker = ResultTyparChecker(fun () -> true)
+ res |> List.map (fun (resInfo, rfref) ->
+ ResolutionInfo.SendEntityPathToSink(sink, ncenv, nenv, ItemOccurrence.UseInType, ad, resInfo, checker)
+ rfref))
/// Resolve a long identifier representing a nested record field.
///
@@ -5214,6 +5229,17 @@ let getRecordFieldsInScope nenv =
Item.RecdField(RecdFieldInfo(typeInsts, fref)))
|> List.ofSeq
+let getRecordTyconsInScope g (ncenv: NameResolver) nenv ad m =
+ [
+ for KeyValue (_, tcref) in nenv.eTyconsByDemangledNameAndArity do
+ if
+ not (tcref.LogicalName.Contains ",") &&
+ tcref.IsRecordTycon &&
+ not (IsTyconUnseen ad g ncenv.amap m false tcref)
+ then
+ tcref, ItemOfTyconRef ncenv m tcref
+ ]
+
/// allowObsolete - specifies whether we should return obsolete types & modules
/// as (no other obsolete items are returned)
let rec ResolvePartialLongIdentToClassOrRecdFields (ncenv: NameResolver) (nenv: NameResolutionEnv) m ad plid (allowObsolete: bool) (fieldsOnly: bool) =
diff --git a/src/Compiler/Checking/NameResolution.fsi b/src/Compiler/Checking/NameResolution.fsi
index 79d1dfbdb49..bfa074d6bac 100755
--- a/src/Compiler/Checking/NameResolution.fsi
+++ b/src/Compiler/Checking/NameResolution.fsi
@@ -842,6 +842,16 @@ val internal ResolveTypeLongIdent:
genOk: PermitDirectReferenceToGeneratedType ->
ResultOrException
+[]
+type internal ExplicitOrSpread<'Explicit, 'Spread> =
+ /// An expression or value derived from an explicit member or record field.
+ | Explicit of 'Explicit
+
+ /// An expression or value derived from a member or field coming from a spread.
+ | Spread of 'Spread
+
+val (|ExplicitOrSpread|): ExplicitOrSpread<'Value, 'Value> -> 'Value
+
/// Resolve a long identifier to a field
val internal ResolveField:
sink: TcResultsSink ->
@@ -849,10 +859,9 @@ val internal ResolveField:
nenv: NameResolutionEnv ->
ad: AccessorDomain ->
ty: TType ->
- mp: Ident list ->
- id: Ident ->
+ fldInfo: ExplicitOrSpread ->
allFields: Ident list ->
- FieldResolution list
+ FieldResolution list option
/// Resolve a long identifier to a nested field
val internal ResolveNestedField:
@@ -878,6 +887,14 @@ val internal ResolveExprLongIdent:
val internal getRecordFieldsInScope: NameResolutionEnv -> Item list
+val internal getRecordTyconsInScope:
+ g: TcGlobals ->
+ ncenv: NameResolver ->
+ nenv: NameResolutionEnv ->
+ ad: AccessorDomain ->
+ m: range ->
+ (TyconRef * Item) list
+
/// Resolve a (possibly incomplete) long identifier to a list of possible class or record fields
val internal ResolvePartialLongIdentToClassOrRecdFields:
NameResolver -> NameResolutionEnv -> range -> AccessorDomain -> string list -> bool -> bool -> Item list
diff --git a/src/Compiler/Checking/NicePrint.fs b/src/Compiler/Checking/NicePrint.fs
index 91751d5c8e5..673a74c82b9 100644
--- a/src/Compiler/Checking/NicePrint.fs
+++ b/src/Compiler/Checking/NicePrint.fs
@@ -1742,7 +1742,7 @@ module InfoMemberPrinting =
let layout,paramLayouts =
match denv.showCsharpCodeAnalysisAttributes, minfo with
- | true, ILMeth(_g,mi,_e) ->
+ | true, (ILMeth(_, mi, _) | MethInfoWithModifiedReturnType(ILMeth(_, mi, _), _)) ->
let methodLayout =
// Render Method attributes and [return:..] attributes on separate lines above (@@) the method definition
PrintTypes.layoutCsharpCodeAnalysisIlAttributes denv (minfo.GetCustomAttrs()) (squareAngleL >> (@@)) layout
diff --git a/src/Compiler/Checking/Spreads.fs b/src/Compiler/Checking/Spreads.fs
new file mode 100644
index 00000000000..19ee2fa821d
--- /dev/null
+++ b/src/Compiler/Checking/Spreads.fs
@@ -0,0 +1,663 @@
+[]
+module internal FSharp.Compiler.Spreads
+
+open System
+open FSharp.Compiler
+open FSharp.Compiler.AccessibilityLogic
+open FSharp.Compiler.CheckRecordSyntaxHelpers
+open FSharp.Compiler.CheckBasics
+open FSharp.Compiler.DiagnosticsLogger
+open FSharp.Compiler.Features
+open FSharp.Compiler.NameResolution
+open FSharp.Compiler.Syntax
+open FSharp.Compiler.SyntaxTreeOps
+open FSharp.Compiler.TcGlobals
+open FSharp.Compiler.Text
+open FSharp.Compiler.TypedTree
+open FSharp.Compiler.TypedTreeOps
+open Internal.Utilities.Library
+
+[]
+module private Patterns =
+ []
+ let LeftwardExplicit = true
+
+ []
+ let NoLeftwardExplicit = false
+
+/// Merges updates to nested record fields on the same level in record copy-and-update.
+///
+/// `CheckRecordSyntaxHelpers.TransformAstForNestedUpdates` expands `{ x with A.B = 10; A.C = "" }`
+///
+/// into
+///
+/// { x with
+/// A = { x.A with B = 10 };
+/// A = { x.A with C = "" }
+/// }
+///
+/// which we here combine into
+///
+/// { x with A = { x.A with B = 10; C = "" } }
+let private (|NestedUpdate|_|) expr2 expr1 =
+ match expr1, expr2 with
+ | SynExpr.Record(baseInfo, copyInfo, fields1, m), SynExpr.Record(recordFields = fields2) ->
+ Some(SynExpr.Record(baseInfo, copyInfo, fields1 @ fields2, m))
+ | SynExpr.AnonRecd(isStruct, copyInfo, fields1, m, trivia), SynExpr.AnonRecd(recordFields = fields2) ->
+ Some(SynExpr.AnonRecd(isStruct, copyInfo, fields1 @ fields2, m, trivia))
+ | _ -> None
+
+/// Functions for checking type spreads.
+[]
+module Types =
+ /// Functions for checking record type spreads.
+ []
+ module Records =
+ /// Typechecks the given list of record fields or spreads.
+ let check checkSpreadsLanguageFeature tcField tcSpread (fieldsAndSpreads: SynFieldOrSpread list) : _ list =
+ let rec loop fields i fieldsAndSpreads =
+ match fieldsAndSpreads with
+ | [] ->
+ fields
+ |> Map.toList
+ |> List.collect (fun (_, (_, dupes)) -> dupes)
+ |> List.sortBy (fun (i, _) -> i)
+ |> List.map (fun (_, r) -> r)
+
+ | SynFieldOrSpread.Field(SynField(idOpt = None)) :: fieldsAndSpreads -> loop fields i fieldsAndSpreads
+
+ | SynFieldOrSpread.Field(SynField(idOpt = Some fieldId) as synField) :: fieldsAndSpreads ->
+ let field, errorAmbiguousShadowing = tcField synField
+
+ let fields =
+ fields
+ |> Map.change fieldId.idText (function
+ | None -> Some(LeftwardExplicit, [ i, field ])
+ | Some(LeftwardExplicit, dupes) ->
+ errorAmbiguousShadowing ()
+ Some(LeftwardExplicit, (i, field) :: dupes)
+ | Some(NoLeftwardExplicit, _dupes) -> Some(LeftwardExplicit, [ i, field ]))
+
+ loop fields (i + 1) fieldsAndSpreads
+
+ | SynFieldOrSpread.Spread(SynTypeSpread(range = m) as synSpread) :: fieldsAndSpreads ->
+ checkSpreadsLanguageFeature m
+
+ let rec collectFieldsFromSpread fields i fieldsFromSpread =
+ match fieldsFromSpread with
+ | [] -> fields, i
+ | (fieldId, field, warnAmbiguousShadowing) :: fieldsFromSpread ->
+ let fields =
+ fields
+ |> Map.change fieldId (function
+ | None -> Some(NoLeftwardExplicit, [ i, field ])
+ | Some(LeftwardExplicit, _dupes) ->
+ warnAmbiguousShadowing ()
+ Some(LeftwardExplicit, [ i, field ])
+ | Some(NoLeftwardExplicit, _dupes) -> Some(NoLeftwardExplicit, [ i, field ]))
+
+ collectFieldsFromSpread fields (i + 1) fieldsFromSpread
+
+ let fields, i = collectFieldsFromSpread fields i (tcSpread synSpread)
+ loop fields i fieldsAndSpreads
+
+ loop Map.empty 0 fieldsAndSpreads
+
+/// Functions for checking value spreads.
+[]
+module Values =
+ /// Functions for checking record spreads.
+ []
+ module Records =
+ let private establishFields checkSpreadsLanguageFeature tcField tcSpread (fieldsAndSpreads: SynExprRecordFieldOrSpread list) =
+ let rec loop fields i spreadSrcTys spreadSrcExprs interveningSpreadSrcs fieldsAndSpreads =
+ match fieldsAndSpreads with
+ | [] ->
+ let fields =
+ fields
+ |> Map.toList
+ |> List.collect (fun (_, (_, _, dupes)) -> dupes)
+ |> List.sortBy (fun (i, _) -> i)
+ |> List.map (fun (_, r) -> r)
+
+ List.rev spreadSrcTys, List.rev spreadSrcExprs, fields
+
+ | SynExprRecordFieldOrSpread.Field(SynExprRecordField(fieldName = _, (* isOk *) false), _) :: _ ->
+ // if we met at least one field that is not syntactically correct - raise ReportedError to transfer control to the recovery routine
+ // raising ReportedError None transfers control to the closest errorRecovery point but do not make any records into log
+ // we assume that parse errors were already reported
+ raise (FSharp.Compiler.DiagnosticsLogger.ReportedError None)
+
+ | SynExprRecordFieldOrSpread.Field((SynExprRecordField(fieldName = synLongId, _; expr = fieldExpr; range = m)), _) :: fieldsAndSpreads ->
+ let interveningSpreadSrc =
+ interveningSpreadSrcs |> Map.tryFind (textOfId (List.head synLongId.LongIdent))
+
+ let fieldId, path, fieldExpr, errorAmbiguousShadowing =
+ tcField interveningSpreadSrc synLongId fieldExpr m
+
+ let fields =
+ let (|NestedUpdate|_|) expr1 expr2 =
+ match expr1, expr2 with
+ | None, _
+ | _, None -> None
+ | Some fieldExpr, Some expr -> (|NestedUpdate|_|) fieldExpr expr
+
+ fields
+ |> Map.change (textOfId fieldId) (function
+ | None -> Some(LeftwardExplicit, fieldExpr, [ i, (fieldId, ExplicitOrSpread.Explicit(path, fieldExpr)) ])
+ | Some(LeftwardExplicit, NestedUpdate fieldExpr combinedExpr, _ :: dupes) ->
+ Some(
+ LeftwardExplicit,
+ Some combinedExpr,
+ (i, (fieldId, ExplicitOrSpread.Explicit(path, Some combinedExpr))) :: dupes
+ )
+ | Some(LeftwardExplicit, _dupeExpr, dupes) ->
+ errorAmbiguousShadowing ()
+
+ Some(LeftwardExplicit, fieldExpr, (i, (fieldId, ExplicitOrSpread.Explicit(path, fieldExpr))) :: dupes)
+ | Some(NoLeftwardExplicit, _dupeExpr, _dupes) ->
+ Some(LeftwardExplicit, fieldExpr, [ i, (fieldId, ExplicitOrSpread.Explicit(path, fieldExpr)) ]))
+
+ loop fields (i + 1) spreadSrcTys spreadSrcExprs interveningSpreadSrcs fieldsAndSpreads
+
+ | SynExprRecordFieldOrSpread.Spread(SynExprSpread(expr = spreadSrcSynExpr; range = m) as synExprSpread, _) :: fieldsAndSpreads ->
+ checkSpreadsLanguageFeature m
+
+ match tcSpread synExprSpread with
+ | Some(spreadSrcExpr, spreadSrcTy, fieldsFromSpread) ->
+ let rec collectFieldsFromSpread fields i interveningSpreadSrcs fieldsFromSpread =
+ match fieldsFromSpread with
+ | [] -> fields, i, interveningSpreadSrcs
+ | (fieldId, field, warnAmbiguousShadowing) :: fieldsFromSpread ->
+ let tys =
+ fields
+ |> Map.change (textOfId fieldId) (function
+ | None -> Some(NoLeftwardExplicit, Some spreadSrcSynExpr, [ i, (fieldId, field) ])
+ | Some(LeftwardExplicit, _existingExpr, _dupes) ->
+ warnAmbiguousShadowing ()
+ Some(LeftwardExplicit, Some spreadSrcSynExpr, [ i, (fieldId, field) ])
+ | Some(NoLeftwardExplicit, _existingExpr, _dupes) ->
+ Some(NoLeftwardExplicit, Some spreadSrcSynExpr, [ i, (fieldId, field) ]))
+
+ let interveningSpreadSrcs =
+ interveningSpreadSrcs
+ |> Map.add (textOfId fieldId) (spreadSrcSynExpr, spreadSrcTy)
+
+ collectFieldsFromSpread tys (i + 1) interveningSpreadSrcs fieldsFromSpread
+
+ let fields, i, interveningSpreadSrcs =
+ collectFieldsFromSpread fields i interveningSpreadSrcs fieldsFromSpread
+
+ loop fields i (spreadSrcTy :: spreadSrcTys) (spreadSrcExpr :: spreadSrcExprs) interveningSpreadSrcs fieldsAndSpreads
+
+ | None -> loop fields i spreadSrcTys spreadSrcExprs interveningSpreadSrcs fieldsAndSpreads
+
+ loop Map.empty 0 [] [] Map.empty fieldsAndSpreads
+
+ /// Typechecks the given list of record fields or spreads.
+ let check
+ TcExprFlex
+ (g: TcGlobals)
+ (env: TcEnv)
+ (cenv: TcFileState)
+ (tpenv: UnscopedTyparEnv)
+ (ad: AccessorDomain)
+ (mWholeExpr: range)
+ withExprOpt
+ overallTy
+ (fieldsAndSpreads: SynExprRecordFieldOrSpread list)
+ =
+ let tcField (spreadSrcOpt: (SynExpr * TType) option) (SynLongIdent(lid, _, _)) exprBeingAssigned m =
+ let isFromNestedUpdate, path, fieldId, field =
+ let srcExprOpt =
+ spreadSrcOpt
+ |> Option.map (fun (spreadSrc, _) -> spreadSrc, (spreadSrc.Range, None))
+ |> Option.orElse withExprOpt
+
+ let srcExprTy =
+ spreadSrcOpt
+ |> Option.map (fun (_, spreadSrcTy) -> spreadSrcTy)
+ |> Option.defaultValue overallTy
+
+ match srcExprOpt, lid, exprBeingAssigned with
+ | _, [ id ], _ -> false, [], id, exprBeingAssigned
+ | Some srcExpr, lid, Some exprBeingAssigned ->
+ let (path, id), exprBeingAssigned =
+ TransformAstForNestedUpdates cenv env srcExprTy lid exprBeingAssigned srcExpr
+
+ true, path, id, Some exprBeingAssigned
+ | _ ->
+ let (path, id) = List.frontAndBack lid
+ false, path, id, exprBeingAssigned
+
+ let isFromSpread = Option.isSome spreadSrcOpt
+
+ let errorAmbiguousShadowing () =
+ if not isFromNestedUpdate || isFromSpread then
+ errorR (Error(FSComp.SR.tcMultipleFieldsInRecord fieldId.idText, m))
+
+ fieldId, path, field, errorAmbiguousShadowing
+
+ let tcSpread (SynExprSpread(expr = expr; range = m)) =
+ let mExpr = expr.Range
+
+ if Option.isSome withExprOpt then
+ errorR (Error(FSComp.SR.tcRecordExprSpreadWithCannotBeUsedWithSpreads (), m))
+
+ let flex = false
+
+ let spreadSrcExpr, _tpenv =
+ TcExprFlex cenv flex false (NewInferenceType g) env tpenv expr
+
+ let tyOfSpreadSrcExpr = tyOfExpr g spreadSrcExpr
+
+ let spreadSrcTyIsNullable =
+ g.checkNullness
+ && (nullnessOfTy g tyOfSpreadSrcExpr).Evaluate() = NullnessInfo.WithNull
+
+ let spreadSrcTyIsRecd =
+ isRecdTy g tyOfSpreadSrcExpr || isAnonRecdTy g tyOfSpreadSrcExpr
+
+ let isValidSpreadSrcTy = not spreadSrcTyIsNullable && spreadSrcTyIsRecd
+
+ if isValidSpreadSrcTy then
+ let spreadSrcAddrExpr, spreadSrc =
+ let srcTyIsStruct = isStructTy g tyOfSpreadSrcExpr
+
+ let spreadSrcAddrVal, spreadSrcAddrExpr =
+ mkCompGenLocal
+ mWholeExpr
+ "spreadSrc"
+ (if srcTyIsStruct then
+ mkByrefTy g tyOfSpreadSrcExpr
+ else
+ tyOfSpreadSrcExpr)
+
+ let wrap, oldAddr, _readonly, _writeonly =
+ mkExprAddrOfExpr g srcTyIsStruct false NeverMutates spreadSrcExpr None m
+
+ spreadSrcAddrExpr, (fun expr -> wrap (mkCompGenLet m spreadSrcAddrVal oldAddr expr))
+
+ let recordFieldsFromSpread =
+ if isRecdTy g tyOfSpreadSrcExpr then
+ ResolveRecordOrClassFieldsOfType cenv.nameResolver m ad tyOfSpreadSrcExpr false
+ else
+ tryDestAnonRecdTy g tyOfSpreadSrcExpr
+ |> ValueOption.map (fun (anonInfo, tys) ->
+ anonInfo.SortedIds
+ |> List.ofArray
+ |> List.mapi (fun i id -> Item.AnonRecdField(anonInfo, tys, i, id.idRange)))
+ |> ValueOption.defaultValue []
+
+ let fields =
+ recordFieldsFromSpread
+ |> List.choose (fun field ->
+ match field with
+ | Item.RecdField fieldInfo ->
+ let fieldExpr =
+ mkRecdFieldGetViaExprAddr (spreadSrcAddrExpr, fieldInfo.RecdFieldRef, fieldInfo.TypeInst, mExpr)
+
+ let fieldId = ident (fieldInfo.RecdField.Id.idText, mExpr)
+ let ty = fieldInfo.FieldType
+
+ let warnAmbiguousShadowing () =
+ let fmtedSpreadField =
+ NicePrint.stringOfRecdField env.DisplayEnv cenv.infoReader fieldInfo.TyconRef fieldInfo.RecdField
+
+ warning (Error(FSComp.SR.tcRecordExprSpreadFieldShadowsExplicitField fmtedSpreadField, m))
+
+ Some(fieldId, ExplicitOrSpread.Spread(ty, fieldExpr), warnAmbiguousShadowing)
+
+ | Item.AnonRecdField(anonInfo, tys, fieldIndex, _) ->
+ let fieldExpr =
+ mkAnonRecdFieldGet g (anonInfo, spreadSrcAddrExpr, tys, fieldIndex, mExpr)
+
+ let fieldId = anonInfo.SortedIds[fieldIndex]
+ let ty = tys[fieldIndex]
+
+ let warnAmbiguousShadowing () =
+ let typars =
+ tryAppTy g ty
+ |> ValueOption.map (snd >> List.choose (tryDestTyparTy g >> ValueOption.toOption))
+ |> ValueOption.defaultValue []
+
+ let fmtedSpreadField =
+ LayoutRender.showL (
+ NicePrint.prettyLayoutOfMemberSig env.DisplayEnv ([], fieldId.idText, typars, [], ty)
+ )
+
+ warning (Error(FSComp.SR.tcRecordExprSpreadFieldShadowsExplicitField fmtedSpreadField, m))
+
+ Some(fieldId, ExplicitOrSpread.Spread(ty, fieldExpr), warnAmbiguousShadowing)
+
+ | _ -> None)
+
+ Some(spreadSrc, tyOfSpreadSrcExpr, fields)
+ else
+ if not expr.IsArbExprAndThusAlreadyReportedError then
+ if not spreadSrcTyIsRecd then
+ errorR (Error(FSComp.SR.tcRecordExprSpreadSourceMustBeRecord (), m))
+ elif spreadSrcTyIsNullable then
+ errorR (Error(FSComp.SR.tcRecordExprSpreadSourceCannotBeNullable (), m))
+
+ None
+
+ let checkSpreadsLanguageFeature m =
+ checkLanguageFeatureAndRecover g.langVersion LanguageFeature.RecordSpreads m
+
+ establishFields checkSpreadsLanguageFeature tcField tcSpread fieldsAndSpreads
+
+ /// Functions for checking anonymous record spreads.
+ module AnonymousRecords =
+ let private establishFields
+ checkSpreadsLanguageFeature
+ tcField
+ tcSpread
+ (targetAnonRecordTy, targetAnonRecordTyContainsField)
+ (fieldsAndSpreads: SynExprAnonRecordFieldOrSpread list)
+ =
+ let rec loop fields i spreadSrcExprs interveningSpreadSrcs fieldsAndSpreads =
+ match fieldsAndSpreads with
+ | [] ->
+ let processedFieldsList = Map.toList fields
+
+ let processedFieldsList =
+ // If the target type is a known anonymous record type,
+ // keep only those fields that are present in that type
+ // or that are explicitly defined in this one.
+ if targetAnonRecordTy then
+ processedFieldsList
+ |> List.filter (function
+ | _, (LeftwardExplicit, _, _) -> true
+ | fieldId, (NoLeftwardExplicit, _, _) -> targetAnonRecordTyContainsField fieldId)
+ else
+ processedFieldsList
+
+ let (|Head|) = List.head
+
+ let fieldsInAlphabeticalOrder =
+ processedFieldsList |> List.sortBy (fun (fieldName, _) -> fieldName)
+
+ let fieldTysInAlphabeticalOrder =
+ fieldsInAlphabeticalOrder
+ |> List.map (fun (_, (_, _, Head(_, (_, fieldTy, _)))) -> fieldTy)
+
+ let fieldIdsInAlphabeticalOrder =
+ fieldsInAlphabeticalOrder
+ |> List.map (fun (_, (_, _, Head(_, (fieldId, _, _)))) -> fieldId)
+
+ let fieldsInSrcOrder =
+ processedFieldsList
+ |> List.collect (fun (_, (_, _, dupes)) -> dupes)
+ |> List.sortBy (fun (i, _) -> i)
+ |> List.map (fun (_, field) -> field)
+
+ List.rev spreadSrcExprs, fieldIdsInAlphabeticalOrder, fieldTysInAlphabeticalOrder, fieldsInSrcOrder
+
+ | SynExprAnonRecordFieldOrSpread.Field(SynExprAnonRecordField(fieldName = synLongId) as synExprAnonRecordField, _) :: fieldsAndSpreads ->
+ let interveningSpreadSrc =
+ interveningSpreadSrcs |> Map.tryFind (textOfId (List.head synLongId.LongIdent))
+
+ let fieldId, fieldTy, transformedFieldExpr, mkTcField, errorAmbiguousShadowing =
+ tcField interveningSpreadSrc synExprAnonRecordField
+
+ let fields =
+ fields
+ |> Map.change (textOfId fieldId) (function
+ | None ->
+ Some(LeftwardExplicit, transformedFieldExpr, [ i, (fieldId, fieldTy, mkTcField transformedFieldExpr) ])
+ | Some(LeftwardExplicit, NestedUpdate transformedFieldExpr groupedExpr, _ :: dupes) ->
+ Some(LeftwardExplicit, groupedExpr, (i, (fieldId, fieldTy, mkTcField groupedExpr)) :: dupes)
+ | Some(LeftwardExplicit, _dupeExpr, dupes) ->
+ errorAmbiguousShadowing ()
+
+ Some(
+ LeftwardExplicit,
+ transformedFieldExpr,
+ (i, (fieldId, fieldTy, mkTcField transformedFieldExpr)) :: dupes
+ )
+ | Some(NoLeftwardExplicit, _dupeExpr, _dupes) ->
+ Some(LeftwardExplicit, transformedFieldExpr, [ i, (fieldId, fieldTy, mkTcField transformedFieldExpr) ]))
+
+ loop fields (i + 1) spreadSrcExprs interveningSpreadSrcs fieldsAndSpreads
+
+ | SynExprAnonRecordFieldOrSpread.Spread(SynExprSpread(expr = spreadSrcSynExpr; range = m), _) :: fieldsAndSpreads ->
+ checkSpreadsLanguageFeature m
+
+ match tcSpread spreadSrcSynExpr m with
+ | Some(spreadSrcExpr, spreadSrcTy, fieldsFromSpread) ->
+ let rec collectFieldsFromSpread fields i interveningSpreadSrcs fieldsFromSpread =
+ match fieldsFromSpread with
+ | [] -> fields, i, interveningSpreadSrcs
+ | (fieldId, fieldTy, tcField, warnAmbiguousShadowing) :: fieldsFromSpread ->
+ let tys =
+ fields
+ |> Map.change (textOfId fieldId) (function
+ | None -> Some(NoLeftwardExplicit, spreadSrcSynExpr, [ i, (fieldId, fieldTy, tcField) ])
+ | Some(LeftwardExplicit, _existingExpr, _dupes) ->
+ warnAmbiguousShadowing ()
+ Some(LeftwardExplicit, spreadSrcSynExpr, [ i, (fieldId, fieldTy, tcField) ])
+ | Some(NoLeftwardExplicit, _existingExpr, _dupes) ->
+ Some(NoLeftwardExplicit, spreadSrcSynExpr, [ i, (fieldId, fieldTy, tcField) ]))
+
+ let interveningSpreadSrcs =
+ interveningSpreadSrcs
+ |> Map.add (textOfId fieldId) (spreadSrcSynExpr, spreadSrcTy)
+
+ collectFieldsFromSpread tys (i + 1) interveningSpreadSrcs fieldsFromSpread
+
+ let fields, i, interveningSpreadSrcs =
+ collectFieldsFromSpread fields i interveningSpreadSrcs fieldsFromSpread
+
+ loop fields i (spreadSrcExpr :: spreadSrcExprs) interveningSpreadSrcs fieldsAndSpreads
+
+ | None -> loop fields i spreadSrcExprs interveningSpreadSrcs fieldsAndSpreads
+
+ loop Map.empty 0 [] Map.empty fieldsAndSpreads
+
+ /// Typechecks the given list of anonymous record fields or spreads.
+ let check
+ TcExprFlex
+ TcAdjustExprForTypeDirectedConversions
+ MustConvertTo
+ UnifyOverallType
+ errorRIfSpreadUsedWithWith
+ (g: TcGlobals)
+ (env: TcEnv)
+ (cenv: TcFileState)
+ (tpenv: UnscopedTyparEnv)
+ (ad: AccessorDomain)
+ (mWholeExpr: range)
+ (maybeAnonRecdTargetTy: (AnonRecdTypeInfo * TType list) voption)
+ (origExprOpt: (SynExpr * BlockSeparator) option)
+ (origExprTyOrOverallTy: TType)
+ (unsortedFieldIdsAndSynExprsGiven: SynExprAnonRecordFieldOrSpread list)
+ =
+ let checkSpreadsLanguageFeature m =
+ checkLanguageFeatureAndRecover g.langVersion LanguageFeature.RecordSpreads m
+
+ let possibleTargetTyAt =
+ match maybeAnonRecdTargetTy with
+ | ValueSome(anonInfo, tys) ->
+ let names = anonInfo.SortedNames
+ let tys = List.toArray tys
+
+ fun name ->
+ let i = Array.BinarySearch(names, name)
+ if i < 0 then ValueNone else ValueSome tys[i]
+ | ValueNone -> fun _ -> ValueNone
+
+ let tcField
+ (spreadSrcOpt: (SynExpr * TType) option)
+ (SynExprAnonRecordField(fieldName = SynLongIdent(fieldLid, _, _) as synLongIdent; expr = expr; range = m))
+ =
+ let isFromNestedUpdate, fieldId, transformedFieldExpr =
+ let srcExpr, srcTy =
+ spreadSrcOpt
+ |> Option.map (fun (spreadSrc, spreadSrcTy) -> (spreadSrc, (spreadSrc.Range, None)), spreadSrcTy)
+ |> Option.orElseWith (fun () -> origExprOpt |> Option.map (fun origExpr -> origExpr, origExprTyOrOverallTy))
+ |> Option.defaultWith (fun () ->
+ (arbExpr ("nestedUpdateSrcExpr", synLongIdent.Range), (synLongIdent.Range, None)), origExprTyOrOverallTy)
+
+ match fieldLid with
+ | [] -> error (Error(FSComp.SR.nrUnexpectedEmptyLongId (), mWholeExpr))
+ | [ id ] -> false, id, expr
+ | lid ->
+ let (_, id), exprBeingAssigned =
+ TransformAstForNestedUpdates cenv env srcTy lid expr srcExpr
+
+ true, id, exprBeingAssigned
+
+ let fieldTy =
+ possibleTargetTyAt fieldId.idText
+ |> ValueOption.defaultWith (fun () -> NewInferenceType g)
+
+ let tcField expr =
+ fun () -> let fieldExpr, _ = TcExprFlex cenv true false fieldTy env tpenv expr in fieldExpr
+
+ let errorAmbiguousShadowing () =
+ if not isFromNestedUpdate then
+ errorR (Error(FSComp.SR.tcAnonRecdDuplicateFieldId fieldId.idText, m))
+
+ fieldId, fieldTy, transformedFieldExpr, tcField, errorAmbiguousShadowing
+
+ let tcSpread (expr: SynExpr) m =
+ errorRIfSpreadUsedWithWith m
+
+ let flex = false
+
+ let spreadSrcExpr, _ =
+ TcExprFlex cenv flex false (NewInferenceType g) env tpenv expr
+
+ let tyOfSpreadSrcExpr = tyOfExpr g spreadSrcExpr
+
+ let spreadSrcTyIsNullable =
+ g.checkNullness
+ && (nullnessOfTy g tyOfSpreadSrcExpr).Evaluate() = NullnessInfo.WithNull
+
+ let spreadSrcTyIsRecd =
+ isRecdTy g tyOfSpreadSrcExpr || isAnonRecdTy g tyOfSpreadSrcExpr
+
+ let isValidSpreadSrcTy = not spreadSrcTyIsNullable && spreadSrcTyIsRecd
+
+ if isValidSpreadSrcTy then
+ let spreadSrcAddrExpr, spreadSrcExpr =
+ let srcTyIsStruct = isStructTy g tyOfSpreadSrcExpr
+
+ let spreadSrcAddrVal, spreadSrcAddrExpr =
+ mkCompGenLocal
+ mWholeExpr
+ "spreadSrc"
+ (if srcTyIsStruct then
+ mkByrefTy g tyOfSpreadSrcExpr
+ else
+ tyOfSpreadSrcExpr)
+
+ let wrap, oldAddr, _readonly, _writeonly =
+ mkExprAddrOfExpr g srcTyIsStruct false NeverMutates spreadSrcExpr None m
+
+ spreadSrcAddrExpr, (fun expr -> wrap (mkCompGenLet m spreadSrcAddrVal oldAddr expr))
+
+ let recordFieldsFromSpread =
+ if isRecdTy g tyOfSpreadSrcExpr then
+ ResolveRecordOrClassFieldsOfType cenv.nameResolver m ad tyOfSpreadSrcExpr false
+ else
+ tryDestAnonRecdTy g tyOfSpreadSrcExpr
+ |> ValueOption.map (fun (anonInfo, tys) ->
+ anonInfo.SortedIds
+ |> List.ofArray
+ |> List.mapi (fun i id -> Item.AnonRecdField(anonInfo, tys, i, id.idRange)))
+ |> ValueOption.defaultValue []
+
+ let fields =
+ recordFieldsFromSpread
+ |> List.choose (fun field ->
+ match field with
+ | Item.RecdField fieldInfo ->
+ let fieldId = fieldInfo.RecdField.Id
+
+ let ty =
+ possibleTargetTyAt fieldId.idText
+ |> ValueOption.defaultValue fieldInfo.FieldType
+
+ let tcField () =
+ let get =
+ mkRecdFieldGetViaExprAddr (spreadSrcAddrExpr, fieldInfo.RecdFieldRef, fieldInfo.TypeInst, m)
+
+ let overallTy = MustConvertTo(false, ty)
+ UnifyOverallType cenv env m overallTy fieldInfo.FieldType
+
+ let fieldExpr =
+ TcAdjustExprForTypeDirectedConversions cenv overallTy fieldInfo.FieldType env m get
+
+ let fieldExpr = mkCoerceIfNeeded g ty (tyOfExpr g fieldExpr) fieldExpr
+ fieldExpr
+
+ let warnAmbiguousShadowing () =
+ let fmtedSpreadField =
+ NicePrint.stringOfRecdField env.DisplayEnv cenv.infoReader fieldInfo.TyconRef fieldInfo.RecdField
+
+ warning (Error(FSComp.SR.tcRecordExprSpreadFieldShadowsExplicitField fmtedSpreadField, m))
+
+ Some(fieldId, ty, tcField, warnAmbiguousShadowing)
+
+ | Item.AnonRecdField(anonInfo, tys, fieldIndex, _) ->
+ let fieldId = anonInfo.SortedIds[fieldIndex]
+
+ let ty =
+ possibleTargetTyAt fieldId.idText
+ |> ValueOption.defaultWith (fun () -> tys[fieldIndex])
+
+ let tcField () =
+ let get =
+ mkAnonRecdFieldGetViaExprAddr (anonInfo, spreadSrcAddrExpr, tys, fieldIndex, m)
+
+ let overallTy = MustConvertTo(false, ty)
+ UnifyOverallType cenv env m overallTy tys[fieldIndex]
+
+ let fieldExpr =
+ TcAdjustExprForTypeDirectedConversions cenv overallTy tys[fieldIndex] env m get
+
+ let fieldExpr = mkCoerceIfNeeded g ty (tyOfExpr g fieldExpr) fieldExpr
+ fieldExpr
+
+ let warnAmbiguousShadowing () =
+ let typars =
+ tryAppTy g ty
+ |> ValueOption.map (snd >> List.choose (tryDestTyparTy g >> ValueOption.toOption))
+ |> ValueOption.defaultValue []
+
+ let fmtedSpreadField =
+ LayoutRender.showL (
+ NicePrint.prettyLayoutOfMemberSig env.DisplayEnv ([], fieldId.idText, typars, [], ty)
+ )
+
+ warning (Error(FSComp.SR.tcRecordExprSpreadFieldShadowsExplicitField fmtedSpreadField, m))
+
+ Some(fieldId, ty, tcField, warnAmbiguousShadowing)
+
+ | _ -> None)
+
+ Some(spreadSrcExpr, tyOfSpreadSrcExpr, fields)
+ else
+ if not expr.IsArbExprAndThusAlreadyReportedError then
+ if not spreadSrcTyIsRecd then
+ errorR (Error(FSComp.SR.tcAnonRecordExprSpreadSourceMustBeRecord (), expr.Range))
+ elif spreadSrcTyIsNullable then
+ errorR (Error(FSComp.SR.tcAnonRecordExprSpreadSourceCannotBeNullable (), m))
+
+ None
+
+ let targetAnonRecordTy, targetAnonRecordTyContainsField =
+ maybeAnonRecdTargetTy
+ |> ValueOption.map (fun (anonInfo, _) ->
+ let sortedNames = anonInfo.SortedNames
+ true, fun fieldId -> Array.BinarySearch(sortedNames, fieldId) >= 0)
+ |> ValueOption.defaultValue (false, fun _ -> false)
+
+ establishFields
+ checkSpreadsLanguageFeature
+ tcField
+ tcSpread
+ (targetAnonRecordTy, targetAnonRecordTyContainsField)
+ unsortedFieldIdsAndSynExprsGiven
diff --git a/src/Compiler/CodeGen/IlxGen.fs b/src/Compiler/CodeGen/IlxGen.fs
index 986acc7bba6..c4fbea22a66 100644
--- a/src/Compiler/CodeGen/IlxGen.fs
+++ b/src/Compiler/CodeGen/IlxGen.fs
@@ -25,6 +25,7 @@ open FSharp.Compiler.AbstractIL.ILX
open FSharp.Compiler.AbstractIL.ILX.Types
open FSharp.Compiler.AttributeChecking
open FSharp.Compiler.CompilerGlobalState
+open FSharp.Compiler.DelegateForwarding
open FSharp.Compiler.DiagnosticsLogger
open FSharp.Compiler.Features
open FSharp.Compiler.Infos
@@ -45,6 +46,13 @@ open FSharp.Compiler.TypedTreeOps.DebugPrint
open FSharp.Compiler.TypeHierarchy
open FSharp.Compiler.TypeRelations
+// Naming wrappers routed through here so synthesized-name replay stays enforceable.
+let private freshIlxName (g: TcGlobals) name m =
+ g.CompilerGlobalState.Value.IlxGenNiceNameGenerator.FreshCompilerGeneratedName(name, m)
+
+let private freshCoreName (g: TcGlobals) name m =
+ g.CompilerGlobalState.Value.NiceNameGenerator.FreshCompilerGeneratedName(name, m)
+
let getEmptyStackGuard () = StackGuard("IlxAssemblyGenerator")
let IsNonErasedTypar (tp: Typar) = not tp.IsErased
@@ -876,16 +884,12 @@ let GenFieldSpecForStaticField (isInteractive, g: TcGlobals, ilContainerTy, vspe
elif g.realsig then
assert (g.CompilerGlobalState |> Option.isSome)
- mkILFieldSpecInTy (
- ilContainerTy,
- CompilerGeneratedName(g.CompilerGlobalState.Value.IlxGenNiceNameGenerator.FreshCompilerGeneratedName(nm, m)),
- ilTy
- )
+ mkILFieldSpecInTy (ilContainerTy, CompilerGeneratedName(freshIlxName g nm m), ilTy)
else
let fieldName =
// Ensure that we have an g.CompilerGlobalState
assert (g.CompilerGlobalState |> Option.isSome)
- g.CompilerGlobalState.Value.IlxGenNiceNameGenerator.FreshCompilerGeneratedName(nm, m)
+ freshIlxName g nm m
let ilFieldContainerTy = mkILTyForCompLoc (CompLocForInitClass cloc)
mkILFieldSpecInTy (ilFieldContainerTy, fieldName, ilTy)
@@ -2261,7 +2265,6 @@ type AnonTypeGenerationTable() =
mkLdfldMethodDef ("get_" + propName, ILMemberAccess.Public, false, ilTy, fldName, fldTy, ILAttributes.Empty, attrs)
|> g.AddMethodGeneratedAttributes
- yield! genToStringMethod ilTy
]
let ilBaseTy = (if isStruct then g.iltyp_ValueType else g.ilg.typ_Object)
@@ -2364,6 +2367,10 @@ type AnonTypeGenerationTable() =
Some(mkLocalValRef augmentation.EqualsExactWithComparer)
)
+ // Generate ToString through the synthetic record tycon (renders "{| Name = value; ... |}" under
+ // --reflectionfree, otherwise sprintf "%+A"). Done here, not in ilMethods above, because it needs the tycon.
+ let ilToStringMethodDefs = genToStringMethod (ilTy, tycon)
+
// Build the ILTypeDef. We don't rely on the normal record generation process because we want very specific field names
let ilTypeDefAttribs =
@@ -2386,7 +2393,7 @@ type AnonTypeGenerationTable() =
ilGenericParams,
ilBaseTy,
ilInterfaceTys,
- mkILMethods (ilCtorDef :: ilMethods),
+ mkILMethods (ilCtorDef :: ilMethods @ ilToStringMethodDefs),
ilFieldDefs,
emptyILTypeDefs,
ilProperties,
@@ -3867,7 +3874,11 @@ and GenAllocRecd cenv cgbuf eenv ctorInfo (tcref, argTys, args, m) sequel =
and GenAllocAnonRecd cenv cgbuf eenv (anonInfo: AnonRecdTypeInfo, tyargs, args, m) sequel =
let anonCtor, _anonMethods, anonType =
- cgbuf.mgbuf.LookupAnonType((fun ilThisTy -> GenToStringMethod cenv eenv ilThisTy m), anonInfo)
+ cgbuf.mgbuf.LookupAnonType(
+ (fun (ilThisTy, tycon) ->
+ GenRecordToStringMethod(cenv, cgbuf.mgbuf, EnvForTycon tycon eenv, ilThisTy, mkLocalTyconRef tycon, m, "{| ", " |}")),
+ anonInfo
+ )
let boxity = anonType.Boxity
GenExprs cenv cgbuf eenv args
@@ -3881,7 +3892,11 @@ and GenAllocAnonRecd cenv cgbuf eenv (anonInfo: AnonRecdTypeInfo, tyargs, args,
and GenGetAnonRecdField cenv cgbuf eenv (anonInfo: AnonRecdTypeInfo, e, tyargs, n, m) sequel =
let _anonCtor, anonMethods, anonType =
- cgbuf.mgbuf.LookupAnonType((fun ilThisTy -> GenToStringMethod cenv eenv ilThisTy m), anonInfo)
+ cgbuf.mgbuf.LookupAnonType(
+ (fun (ilThisTy, tycon) ->
+ GenRecordToStringMethod(cenv, cgbuf.mgbuf, EnvForTycon tycon eenv, ilThisTy, mkLocalTyconRef tycon, m, "{| ", " |}")),
+ anonInfo
+ )
let boxity = anonType.Boxity
let ilTypeArgs = GenTypeArgs cenv m eenv.tyenv tyargs
@@ -4693,7 +4708,7 @@ and GenApp (cenv: cenv) cgbuf eenv (f, fty, tyargs, curriedArgs, m) sequel =
let locName =
// Ensure that we have an g.CompilerGlobalState
assert (g.CompilerGlobalState |> Option.isSome)
- g.CompilerGlobalState.Value.IlxGenNiceNameGenerator.FreshCompilerGeneratedName("arg", m), ilTy, false
+ freshIlxName g "arg" m, ilTy, false
let loc, _realloc, eenv = AllocLocal cenv cgbuf eenv true locName scopeMarks
GenExpr cenv cgbuf eenv laterArg Continue
@@ -5030,13 +5045,7 @@ and GenTry cenv cgbuf eenv scopeMarks (e1, m, resultTy, spTry) =
assert (cenv.g.CompilerGlobalState |> Option.isSome)
let whereToSave, _realloc, eenvinner =
- AllocLocal
- cenv
- cgbuf
- eenvinner
- true
- (cenv.g.CompilerGlobalState.Value.IlxGenNiceNameGenerator.FreshCompilerGeneratedName("tryres", m), ilResultTy, false)
- (startTryMark, endTryMark)
+ AllocLocal cenv cgbuf eenvinner true (freshIlxName cenv.g "tryres" m, ilResultTy, false) (startTryMark, endTryMark)
Some(whereToSave, ilResultTy), eenvinner
@@ -5311,8 +5320,7 @@ and GenIntegerForLoop cenv cgbuf eenv (spFor, spTo, v, e1, dir, e2, loopBody, m)
// Ensure that we have an g.CompilerGlobalState
assert (g.CompilerGlobalState |> Option.isSome)
- let vName =
- g.CompilerGlobalState.Value.IlxGenNiceNameGenerator.FreshCompilerGeneratedName("endLoop", m)
+ let vName = freshIlxName g "endLoop" m
let v, _realloc, eenvinner =
AllocLocal cenv cgbuf eenvinner true (vName, g.ilg.typ_Int32, false) (start, finish)
@@ -5940,13 +5948,7 @@ and GenDefaultValue cenv cgbuf eenv (ty, m) =
// Ensure that we have an g.CompilerGlobalState
assert (g.CompilerGlobalState |> Option.isSome)
- AllocLocal
- cenv
- cgbuf
- eenv
- true
- (g.CompilerGlobalState.Value.IlxGenNiceNameGenerator.FreshCompilerGeneratedName("default", m), ilTy, false)
- scopeMarks
+ AllocLocal cenv cgbuf eenv true (freshIlxName g "default" m, ilTy, false) scopeMarks
// We can normally rely on .NET IL zero-initialization of the temporaries
// we create to get zero values for struct types.
//
@@ -6625,25 +6627,11 @@ and GenStructStateMachine cenv cgbuf eenvouter (res: LoweredStateMachine) sequel
// The local for the state machine
let locIdx, realloc, _ =
- AllocLocal
- cenv
- cgbuf
- eenvouter
- true
- (g.CompilerGlobalState.Value.IlxGenNiceNameGenerator.FreshCompilerGeneratedName("machine", m), ilCloTy, false)
- scopeMarks
+ AllocLocal cenv cgbuf eenvouter true (freshIlxName g "machine" m, ilCloTy, false) scopeMarks
// The local for the state machine address
let locIdx2, _realloc2, _ =
- AllocLocal
- cenv
- cgbuf
- eenvouter
- true
- (g.CompilerGlobalState.Value.IlxGenNiceNameGenerator.FreshCompilerGeneratedName(afterCodeThisVar.DisplayName, m),
- ilMachineAddrTy,
- false)
- scopeMarks
+ AllocLocal cenv cgbuf eenvouter true (freshIlxName g afterCodeThisVar.DisplayName m, ilMachineAddrTy, false) scopeMarks
let eenvouter =
eenvouter
@@ -7537,136 +7525,303 @@ and GenDelegateExpr cenv cgbuf eenvouter expr (TObjExprMethod(slotsig, _attribs,
with _ ->
false
- // Work out the free type variables for the morphing thunk
- let takenNames = List.map nameOfVal tmvs
+ let invokeParamInfos =
+ List.replicate (List.concat slotsig.FormalParams).Length ValReprInfo.unnamedTopArg1
- let cloFreeTyvars, cloWitnessInfos, cloFreeVars, ilDelegeeTypeRef, ilCloAllFreeVars, eenvinner =
- GetIlxClosureFreeVars cenv m [] ILBoxity.AsObject eenvouter takenNames expr
+ let numDelegeeParams = invokeParamInfos.Length
- let ilDelegeeGenericParams = GenGenericParams cenv eenvinner cloFreeTyvars
- let ilDelegeeGenericActualsInner = mkILFormalGenericArgs 0 ilDelegeeGenericParams
+ let etaUnitDelegate =
+ match tmvs, invokeParamInfos with
+ | [ _ ], [] -> true
+ | _ -> false
- // When creating a delegate that does not capture any variables, we can instead create a static closure and directly reference the method.
- let useStaticClosure = cloFreeVars.IsEmpty
+ let tmvs, body = BindUnitVars g (tmvs, invokeParamInfos, body)
- // Create a new closure class with a single "delegee" method that implements the delegate.
- let delegeeMethName = "Invoke"
- let ilDelegeeTyInner = mkILBoxedTy ilDelegeeTypeRef ilDelegeeGenericActualsInner
+ // Point the delegate directly at a recognized transparent-forwarding target instead of generating an
+ // intermediate closure; anything unmatched falls back to the closure path below.
+ let directDelegateTarget =
+ if not (g.langVersion.SupportsFeature LanguageFeature.DirectDelegateConstruction) then
+ None
+ elif
+ not cenv.options.localOptimizationsEnabled
+ && (etaUnitDelegate || tmvs |> List.exists (fun v -> not v.IsCompilerGenerated))
+ then
+ // Keep eta-expanded delegates as closures in unoptimized builds so the user's lambda parameter
+ // names survive for debugging; non-eta parameters are synthesized, so nothing is lost there.
+ None
+ else
+ match classifyForwardingTarget (Optimizer.ExprHasEffect Optimizer.EffectContext.Emit) g tmvs body with
+ | DirectDelegateForwardingTargetCandidate.FSharpVal(vref, valUseFlags, tyargs, leadingArgs) ->
+ match StorageForValRef m vref eenvouter with
+ | Method(valReprInfo, vrefM, mspec, _, _, ctps, _, _, _, _, _, _) ->
+ let _, witnessInfos, _, _, _ =
+ GetValReprTypeInCompiledForm g valReprInfo ctps.Length vrefM.Type m
- let envForDelegeeUnderTypars = AddTyparsToEnv methTyparsOfOverridingMethod eenvinner
+ let hasWitnesses = ComputeGenerateWitnesses g eenvouter && not witnessInfos.IsEmpty
- let numthis = if useStaticClosure then 0 else 1
+ match
+ fsharpValDirectlyBindable
+ (Optimizer.ExprHasEffect Optimizer.EffectContext.Emit)
+ g
+ tmvs
+ leadingArgs
+ vrefM
+ valUseFlags
+ hasWitnesses
+ with
+ | ValueSome(virtualCall, takesInstanceArg) ->
+ let ilTyArgs = GenTypeArgs cenv m eenvouter.tyenv tyargs
- let tmvs, body =
- BindUnitVars g (tmvs, List.replicate (List.concat slotsig.FormalParams).Length ValReprInfo.unnamedTopArg1, body)
+ let numEnclILTypeArgs =
+ if vrefM.MemberInfo.IsSome && not vrefM.IsExtensionMember then
+ List.length (vrefM.MemberApparentEntity.Typars |> DropErasedTypars)
+ else
+ 0
- // The slot sig contains a formal instantiation. When creating delegates we're only
- // interested in the actual instantiation since we don't have to emit a method impl.
- let ilDelegeeParams, ilDelegeeRet =
- GenActualSlotsig m cenv envForDelegeeUnderTypars slotsig methTyparsOfOverridingMethod tmvs
+ if ilTyArgs.Length < numEnclILTypeArgs then
+ None
+ else
+ let ilEnclArgTys, ilMethArgTys = List.splitAt numEnclILTypeArgs ilTyArgs
- let envForDelegeeMeth =
- AddStorageForLocalVals g (List.mapi (fun i v -> (v, Arg(i + numthis))) tmvs) envForDelegeeUnderTypars
+ let targetMspec =
+ mkILMethSpec (mspec.MethodRef, mspec.DeclaringType.Boxity, ilEnclArgTys, ilMethArgTys)
- let ilMethodBody =
- CodeGenMethodForExpr
- cenv
- cgbuf.mgbuf
- ([],
- delegeeMethName,
- envForDelegeeMeth,
- 1,
- None,
- body,
- (if slotSigHasVoidReturnTy slotsig then
- discardAndReturnVoid
- else
- Return))
+ let numBoundLeadingFormals = if takesInstanceArg then 0 else leadingArgs.Length
- let delegeeInvokeMeth =
- (if useStaticClosure then
- mkILNonGenericStaticMethod
- else
- mkILNonGenericInstanceMethod) (
- delegeeMethName,
- ILMemberAccess.Assembly,
- ilDelegeeParams,
- ilDelegeeRet,
- MethodBody.IL(InterruptibleLazy.FromValue ilMethodBody)
- )
+ if takesInstanceArg <> targetMspec.MethodRef.CallingConv.IsInstance then
+ None
+ else
+ let ilDelegeeRetTy =
+ let envUnderTypars = AddTyparsToEnv methTyparsOfOverridingMethod eenvouter
- let delegeeCtorMeth =
- mkILSimpleStorageCtor (Some g.ilg.typ_Object.TypeSpec, ilDelegeeTyInner, [], [], ILMemberAccess.Assembly, None, eenvouter.imports)
+ let _, ilDelegeeRet =
+ GenActualSlotsig m cenv envUnderTypars slotsig methTyparsOfOverridingMethod tmvs
- let ilCtorBody = delegeeCtorMeth.MethodBody
+ ilDelegeeRet.Type
- let ilCloLambdas = Lambdas_return ilCtxtDelTy
+ if
+ signatureMatches
+ numBoundLeadingFormals
+ numDelegeeParams
+ ilDelegeeRetTy
+ ilEnclArgTys
+ ilMethArgTys
+ targetMspec
+ then
+ Some(targetMspec, receiverInfo leadingArgs virtualCall takesInstanceArg)
+ else
+ None
+ | ValueNone -> None
+ | _ -> None
- let cloTypeDefs =
- (if useStaticClosure then
- GenStaticDelegateClosureTypeDefs
- else
- GenClosureTypeDefs)
- cenv
- (ilDelegeeTypeRef,
- ilDelegeeGenericParams,
- [],
- ilCloAllFreeVars,
- ilCloLambdas,
- ilCtorBody,
- [ delegeeInvokeMeth ],
- [],
- g.ilg.typ_Object,
- [],
- None)
+ | DirectDelegateForwardingTargetCandidate.ILMethod(isVirtual,
+ isStruct,
+ isCtor,
+ valUseFlag,
+ ilMethRef,
+ enclTypeInst,
+ methInst,
+ leadingArgs) ->
+ if
+ ilMethodDirectlyBindable
+ (Optimizer.ExprHasEffect Optimizer.EffectContext.Emit)
+ g
+ tmvs
+ leadingArgs
+ ilMethRef
+ valUseFlag
+ isCtor
+ then
+ let ilEnclArgTys = GenTypeArgs cenv m eenvouter.tyenv enclTypeInst
+ let ilMethArgTys = GenTypeArgs cenv m eenvouter.tyenv methInst
+ let boxity = if isStruct then AsValue else AsObject
+ let targetMspec = mkILMethSpec (ilMethRef, boxity, ilEnclArgTys, ilMethArgTys)
+
+ let numBoundLeadingFormals =
+ if ilMethRef.CallingConv.IsInstance then
+ 0
+ else
+ leadingArgs.Length
- for cloTypeDef in cloTypeDefs do
- cgbuf.mgbuf.AddTypeDef(ilDelegeeTypeRef, cloTypeDef, false, false, None, m)
+ // Imported metadata carries different assembly scope refs than the compiler-generated
+ // delegee types, so structural IL type comparison reports false negatives even for
+ // primitives; the arity check is the sound residual guard (the call is already typed).
+ if targetMspec.FormalArgTypes.Length - numBoundLeadingFormals = numDelegeeParams then
+ Some(targetMspec, receiverInfo leadingArgs isVirtual ilMethRef.CallingConv.IsInstance)
+ else
+ None
+ else
+ None
- CountClosure()
+ | DirectDelegateForwardingTargetCandidate.Other -> None
- // Push the constructor for the delegee
- let ctxtGenericArgsForDelegee = GenGenericArgs m eenvouter.tyenv cloFreeTyvars
+ match directDelegateTarget with
+ | Some(targetMspec, receiverInfo) ->
+ match receiverInfo with
+ | None ->
+ // Static target: null Target.
+ GenUnit cenv eenvouter m cgbuf
+ CG.EmitInstr cgbuf (pop 0) (Push [ g.ilg.typ_IntPtr ]) (I_ldftn targetMspec)
+ | Some(receiverExpr, isVirtual, isInstanceReceiver) ->
+ // The leading argument becomes the Target: an instance receiver, or a static method's closed-over first argument.
+ GenExpr cenv cgbuf eenvouter receiverExpr Continue
+
+ if isInstanceReceiver && targetMspec.DeclaringType.Boxity.IsAsValue then
+ // Box a copy of a value-type instance receiver as the 'object' Target; invocation reaches 'this'
+ // through the runtime's unboxing stub, matching the closure's by-value capture. Only an instance
+ // receiver is boxed - a static closed-over first argument is already a reference.
+ CG.EmitInstr cgbuf (pop 1) (Push [ g.ilg.typ_Object ]) (I_box targetMspec.DeclaringType)
+
+ if isVirtual then
+ // dup the receiver so ldvirtftn can bind its runtime type's override.
+ CG.EmitInstr cgbuf (pop 0) (Push [ targetMspec.DeclaringType ]) AI_dup
+ CG.EmitInstr cgbuf (pop 1) (Push [ g.ilg.typ_IntPtr ]) (I_ldvirtftn targetMspec)
+ else
+ CG.EmitInstr cgbuf (pop 0) (Push [ g.ilg.typ_IntPtr ]) (I_ldftn targetMspec)
- if useStaticClosure then
- GenUnit cenv eenvouter m cgbuf
- else
- let ilxCloSpec =
- IlxClosureSpec.Create(IlxClosureRef(ilDelegeeTypeRef, ilCloLambdas, ilCloAllFreeVars), ctxtGenericArgsForDelegee, false)
+ // newobj Delegate::.ctor(object, native int)
+ let ilDelegeeCtorMethOuter =
+ mkCtorMethSpecForDelegate g.ilg (ilCtxtDelTy, useUIntPtrForDelegateCtor)
- GenWitnessArgsFromWitnessInfos cenv cgbuf eenvouter m cloWitnessInfos
+ CG.EmitInstr cgbuf (pop 2) (Push [ ilCtxtDelTy ]) (I_newobj(ilDelegeeCtorMethOuter, None))
+ GenSequel cenv eenvouter.cloc cgbuf sequel
- for fv in cloFreeVars do
- GenGetFreeVarForClosure cenv cgbuf eenvouter m fv
+ | None ->
+ let takenNames = List.map nameOfVal tmvs
- CG.EmitInstr
- cgbuf
- (pop ilCloAllFreeVars.Length)
- (Push [ EraseClosures.mkTyOfLambdas cenv.ilxPubCloEnv ilCloLambdas ])
- (I_newobj(ilxCloSpec.Constructor, None))
+ // Work out the free type variables for the morphing thunk
+ let cloFreeTyvars, cloWitnessInfos, cloFreeVars, ilDelegeeTypeRef, ilCloAllFreeVars, eenvinner =
+ GetIlxClosureFreeVars cenv m [] ILBoxity.AsObject eenvouter takenNames expr
- // Push the function pointer to the Invoke method of the delegee
- let ilDelegeeTyOuter = mkILBoxedTy ilDelegeeTypeRef ctxtGenericArgsForDelegee
+ let ilDelegeeGenericParams = GenGenericParams cenv eenvinner cloFreeTyvars
+ let ilDelegeeGenericActualsInner = mkILFormalGenericArgs 0 ilDelegeeGenericParams
- let ilDelegeeInvokeMethOuter =
- (if useStaticClosure then
- mkILNonGenericStaticMethSpecInTy
- else
- mkILNonGenericInstanceMethSpecInTy) (
- ilDelegeeTyOuter,
- "Invoke",
- typesOfILParams ilDelegeeParams,
- ilDelegeeRet.Type
- )
+ // When creating a delegate that does not capture any variables, we can instead create a static closure and directly reference the method.
+ let useStaticClosure = cloFreeVars.IsEmpty
- CG.EmitInstr cgbuf (pop 0) (Push [ g.ilg.typ_IntPtr ]) (I_ldftn ilDelegeeInvokeMethOuter)
+ // Create a new closure class with a single "delegee" method that implements the delegate.
+ let delegeeMethName = "Invoke"
+ let ilDelegeeTyInner = mkILBoxedTy ilDelegeeTypeRef ilDelegeeGenericActualsInner
- // Instantiate the delegate
- let ilDelegeeCtorMethOuter =
- mkCtorMethSpecForDelegate g.ilg (ilCtxtDelTy, useUIntPtrForDelegateCtor)
+ let envForDelegeeUnderTypars = AddTyparsToEnv methTyparsOfOverridingMethod eenvinner
- CG.EmitInstr cgbuf (pop 2) (Push [ ilCtxtDelTy ]) (I_newobj(ilDelegeeCtorMethOuter, None))
- GenSequel cenv eenvouter.cloc cgbuf sequel
+ let numthis = if useStaticClosure then 0 else 1
+
+ // The slot sig contains a formal instantiation. When creating delegates we're only
+ // interested in the actual instantiation since we don't have to emit a method impl.
+ let ilDelegeeParams, ilDelegeeRet =
+ GenActualSlotsig m cenv envForDelegeeUnderTypars slotsig methTyparsOfOverridingMethod tmvs
+
+ let envForDelegeeMeth =
+ AddStorageForLocalVals g (List.mapi (fun i v -> (v, Arg(i + numthis))) tmvs) envForDelegeeUnderTypars
+
+ let ilMethodBody =
+ CodeGenMethodForExpr
+ cenv
+ cgbuf.mgbuf
+ ([],
+ delegeeMethName,
+ envForDelegeeMeth,
+ 1,
+ None,
+ body,
+ (if slotSigHasVoidReturnTy slotsig then
+ discardAndReturnVoid
+ else
+ Return))
+
+ let delegeeInvokeMeth =
+ (if useStaticClosure then
+ mkILNonGenericStaticMethod
+ else
+ mkILNonGenericInstanceMethod) (
+ delegeeMethName,
+ ILMemberAccess.Assembly,
+ ilDelegeeParams,
+ ilDelegeeRet,
+ MethodBody.IL(InterruptibleLazy.FromValue ilMethodBody)
+ )
+
+ let delegeeCtorMeth =
+ mkILSimpleStorageCtor (
+ Some g.ilg.typ_Object.TypeSpec,
+ ilDelegeeTyInner,
+ [],
+ [],
+ ILMemberAccess.Assembly,
+ None,
+ eenvouter.imports
+ )
+
+ let ilCtorBody = delegeeCtorMeth.MethodBody
+
+ let ilCloLambdas = Lambdas_return ilCtxtDelTy
+
+ let cloTypeDefs =
+ (if useStaticClosure then
+ GenStaticDelegateClosureTypeDefs
+ else
+ GenClosureTypeDefs)
+ cenv
+ (ilDelegeeTypeRef,
+ ilDelegeeGenericParams,
+ [],
+ ilCloAllFreeVars,
+ ilCloLambdas,
+ ilCtorBody,
+ [ delegeeInvokeMeth ],
+ [],
+ g.ilg.typ_Object,
+ [],
+ None)
+
+ for cloTypeDef in cloTypeDefs do
+ cgbuf.mgbuf.AddTypeDef(ilDelegeeTypeRef, cloTypeDef, false, false, None, m)
+
+ CountClosure()
+
+ // Push the constructor for the delegee
+ let ctxtGenericArgsForDelegee = GenGenericArgs m eenvouter.tyenv cloFreeTyvars
+
+ if useStaticClosure then
+ GenUnit cenv eenvouter m cgbuf
+ else
+ let ilxCloSpec =
+ IlxClosureSpec.Create(IlxClosureRef(ilDelegeeTypeRef, ilCloLambdas, ilCloAllFreeVars), ctxtGenericArgsForDelegee, false)
+
+ GenWitnessArgsFromWitnessInfos cenv cgbuf eenvouter m cloWitnessInfos
+
+ for fv in cloFreeVars do
+ GenGetFreeVarForClosure cenv cgbuf eenvouter m fv
+
+ CG.EmitInstr
+ cgbuf
+ (pop ilCloAllFreeVars.Length)
+ (Push [ EraseClosures.mkTyOfLambdas cenv.ilxPubCloEnv ilCloLambdas ])
+ (I_newobj(ilxCloSpec.Constructor, None))
+
+ // Push the function pointer to the Invoke method of the delegee
+ let ilDelegeeTyOuter = mkILBoxedTy ilDelegeeTypeRef ctxtGenericArgsForDelegee
+
+ let ilDelegeeInvokeMethOuter =
+ (if useStaticClosure then
+ mkILNonGenericStaticMethSpecInTy
+ else
+ mkILNonGenericInstanceMethSpecInTy) (
+ ilDelegeeTyOuter,
+ "Invoke",
+ typesOfILParams ilDelegeeParams,
+ ilDelegeeRet.Type
+ )
+
+ CG.EmitInstr cgbuf (pop 0) (Push [ g.ilg.typ_IntPtr ]) (I_ldftn ilDelegeeInvokeMethOuter)
+
+ // Instantiate the delegate
+ let ilDelegeeCtorMethOuter =
+ mkCtorMethSpecForDelegate g.ilg (ilCtxtDelTy, useUIntPtrForDelegateCtor)
+
+ CG.EmitInstr cgbuf (pop 2) (Push [ ilCtxtDelTy ]) (I_newobj(ilDelegeeCtorMethOuter, None))
+ GenSequel cenv eenvouter.cloc cgbuf sequel
/// Used to search FSharp.Core implementations of "^T : ^T" and decide whether the conditional activates
and ExprIsTraitCall expr =
@@ -9412,7 +9567,7 @@ and GenParams
if takenNames.Contains(id.idText) then
// Ensure that we have an g.CompilerGlobalState
assert (g.CompilerGlobalState |> Option.isSome)
- g.CompilerGlobalState.Value.NiceNameGenerator.FreshCompilerGeneratedName(id.idText, id.idRange)
+ freshCoreName g id.idText id.idRange
else
id.idText
@@ -10481,13 +10636,7 @@ and EmitSaveStack cenv cgbuf eenv m scopeMarks =
// Ensure that we have an g.CompilerGlobalState
assert (cenv.g.CompilerGlobalState |> Option.isSome)
- AllocLocal
- cenv
- cgbuf
- eenv
- true
- (cenv.g.CompilerGlobalState.Value.IlxGenNiceNameGenerator.FreshCompilerGeneratedName("spill", m), ty, false)
- scopeMarks
+ AllocLocal cenv cgbuf eenv true (freshIlxName cenv.g "spill" m, ty, false) scopeMarks
idx, eenv)
@@ -10982,7 +11131,11 @@ and GenImplFile cenv (mgbuf: AssemblyBuilder) mainInfoOpt eenv (implFile: Checke
// Generate all the anonymous record types mentioned anywhere in this module
for anonInfo in anonRecdTypes.Values do
- mgbuf.GenerateAnonType((fun ilThisTy -> GenToStringMethod cenv eenv ilThisTy m), anonInfo)
+ mgbuf.GenerateAnonType(
+ (fun (ilThisTy, tycon) ->
+ GenRecordToStringMethod(cenv, mgbuf, EnvForTycon tycon eenv, ilThisTy, mkLocalTyconRef tycon, m, "{| ", " |}")),
+ anonInfo
+ )
let withQName (loc: CompileLocation) =
{ loc with
@@ -11350,11 +11503,8 @@ and GenAbstractBinding cenv eenv tref (vref: ValRef) =
else
[], [], []
-and GenToStringMethod cenv eenv ilThisTy m =
- GenPrintingMethod cenv eenv "ToString" ilThisTy m
-
/// Generate a ToString/get_Message method that calls 'sprintf "%A"'
-and GenPrintingMethod cenv eenv methName ilThisTy m =
+and GenSprintfPrintingMethod cenv eenv methName ilThisTy m =
let g = cenv.g
[
@@ -11419,6 +11569,42 @@ and GenPrintingMethod cenv eenv methName ilThisTy m =
| _ -> ()
]
+/// Emit a [] virtual ToString override whose body is the given string-typed expression.
+/// 'thisv' is the 'this' value (stored at arg 0) referenced by bodyExpr.
+and EmitToStringMethodDef (cenv: cenv, mgbuf: AssemblyBuilder, eenv: IlxGenEnv, thisv: Val, bodyExpr: Expr) =
+ let g = cenv.g
+ let eenvForMeth = AddStorageForLocalVals g [ (thisv, Arg 0) ] eenv
+
+ let ilMethodBody =
+ CodeGenMethodForExpr cenv mgbuf ([], "ToString", eenvForMeth, 0, Some thisv, bodyExpr, Return)
+
+ let mdef =
+ mkILNonGenericVirtualInstanceMethod (
+ "ToString",
+ ILMemberAccess.Public,
+ [],
+ mkILReturn g.ilg.typ_String,
+ MethodBody.IL(InterruptibleLazy.FromValue ilMethodBody)
+ )
+
+ [ mdef.With(customAttrs = mkILCustomAttrs [ g.CompilerGeneratedAttribute ]) ]
+
+/// Generate an anonymous record's ToString as a single line "{| F1 = v1; F2 = v2 |}". Nominal records and
+/// unions get their reflection-free ToString from the type-augmentation phase instead (so the 'string'
+/// operator calls are optimized), but anonymous record types are synthesized too late for that, so they are
+/// generated here. Under non-reflection-free codegen, falls back to sprintf "%+A".
+and GenRecordToStringMethod
+ (cenv: cenv, mgbuf: AssemblyBuilder, eenv: IlxGenEnv, ilThisTy: ILType, tcref: TyconRef, m: range, openBrace: string, closeBrace: string) =
+ let g = cenv.g
+
+ if not g.useReflectionFreeCodeGen then
+ GenSprintfPrintingMethod cenv eenv "ToString" ilThisTy m
+ else
+ let thisv, body =
+ AugmentTypeDefinitions.mkRecdToString (g, tcref, tcref.Deref, openBrace, closeBrace)
+
+ EmitToStringMethodDef(cenv, mgbuf, eenv, thisv, body)
+
and GenTypeDef cenv mgbuf lazyInitInfo eenv m (tycon: Tycon) : ILTypeRef option =
let g = cenv.g
let tcref = mkLocalTyconRef tycon
@@ -12002,8 +12188,10 @@ and GenTypeDef cenv mgbuf lazyInitInfo eenv m (tycon: Tycon) : ILTypeRef option
then
yield mkILSimpleStorageCtor (Some g.ilg.typ_Object.TypeSpec, ilThisTy, [], [], reprAccess, None, eenv.imports)
- if not (tycon.HasMember g "ToString" []) then
- yield! GenToStringMethod cenv eenv ilThisTy m
+ // Reflection-free nominal records get their ToString from the type-augmentation phase; here we
+ // only emit the sprintf "%+A" ToString for the non-reflection-free case.
+ if not g.useReflectionFreeCodeGen && not (tycon.HasMember g "ToString" []) then
+ yield! GenSprintfPrintingMethod cenv eenvinner "ToString" ilThisTy m
| TFSharpTyconRepr r when tycon.IsFSharpDelegateTycon ->
@@ -12026,8 +12214,12 @@ and GenTypeDef cenv mgbuf lazyInitInfo eenv m (tycon: Tycon) : ILTypeRef option
yield! mkILDelegateMethods reprAccess g.ilg (g.iltyp_AsyncCallback, g.iltyp_IAsyncResult) (parameters, ret)
| _ -> ()
- | TFSharpTyconRepr { fsobjmodel_kind = TFSharpUnion } when not (tycon.HasMember g "ToString" []) ->
- yield! GenToStringMethod cenv eenv ilThisTy m
+ // Reflection-free nominal unions get their ToString from the type-augmentation phase; here we
+ // only emit the sprintf "%+A" ToString for the non-reflection-free case.
+ | TFSharpTyconRepr { fsobjmodel_kind = TFSharpUnion } when
+ not g.useReflectionFreeCodeGen && not (tycon.HasMember g "ToString" [])
+ ->
+ yield! GenSprintfPrintingMethod cenv eenvinner "ToString" ilThisTy m
| _ -> ()
]
@@ -12258,18 +12450,10 @@ and GenTypeDef cenv mgbuf lazyInitInfo eenv m (tycon: Tycon) : ILTypeRef option
}
let layout =
- // Structs with no instance fields get size 1, pack 0
+ // Multi-case struct unions carry a hidden tag field; single-case struct unions
+ // are handled by the CLR's minimum-1-byte guarantee. No explicit size needed.
if isStructTy g thisTy then
- if
- (tycon.AllFieldsArray.Length = 0
- || tycon.AllFieldsArray |> Array.exists (fun f -> not f.IsStatic))
- && (alternatives
- |> Array.collect (fun a -> a.FieldDefs)
- |> Array.exists (fun fd -> not fd.ILField.IsStatic))
- then
- ILTypeDefLayout.Sequential { Size = None; Pack = None }
- else
- ILTypeDefLayout.Sequential { Size = Some 1; Pack = Some 0us }
+ ILTypeDefLayout.Sequential { Size = None; Pack = None }
else
ILTypeDefLayout.Auto
@@ -12651,7 +12835,7 @@ and GenExnDef cenv mgbuf eenv m (exnc: Tycon) : ILTypeRef option =
&& not (exnc.HasMember g "Message" [])
&& not (fspecs |> List.exists (fun rf -> rf.DisplayNameCore = "Message"))
then
- yield! GenPrintingMethod cenv eenv "get_Message" ilThisTy m
+ yield! GenSprintfPrintingMethod cenv eenv "get_Message" ilThisTy m
]
let interfaces =
diff --git a/src/Compiler/DependencyManager/AssemblyResolveHandler.fs b/src/Compiler/DependencyManager/AssemblyResolveHandler.fs
index 6daf749f87f..d59b65d835e 100644
--- a/src/Compiler/DependencyManager/AssemblyResolveHandler.fs
+++ b/src/Compiler/DependencyManager/AssemblyResolveHandler.fs
@@ -54,7 +54,7 @@ type AssemblyResolveHandlerCoreclr(assemblyProbingPaths: AssemblyResolutionProbe
let assemblyPathOpt =
assemblyPaths
- |> Seq.tryFind (fun path -> Path.GetFileNameWithoutExtension(path) = simpleName)
+ |> Seq.tryFind (fun path -> String.Equals(Path.GetFileNameWithoutExtension(path), simpleName))
match assemblyPathOpt with
| Some path -> loadAssembly path
@@ -84,7 +84,7 @@ type AssemblyResolveHandlerDeskTop(assemblyProbingPaths: AssemblyResolutionProbe
let assemblyPathOpt =
assemblyPaths
- |> Seq.tryFind (fun path -> Path.GetFileNameWithoutExtension(path) = simpleName)
+ |> Seq.tryFind (fun path -> String.Equals(Path.GetFileNameWithoutExtension(path), simpleName))
match assemblyPathOpt with
| Some path -> Assembly.LoadFrom path
diff --git a/src/Compiler/Driver/CompilerConfig.fs b/src/Compiler/Driver/CompilerConfig.fs
index a1e7937b0d9..7e04ef173f6 100644
--- a/src/Compiler/Driver/CompilerConfig.fs
+++ b/src/Compiler/Driver/CompilerConfig.fs
@@ -598,8 +598,6 @@ type TcConfigBuilder =
/// If true - every expression in quotations will be augmented with full debug info (fileName, location in file)
mutable emitDebugInfoInQuotations: bool
- mutable strictIndentation: bool option
-
mutable alwaysInline: bool option
mutable exename: string option
@@ -854,7 +852,6 @@ type TcConfigBuilder =
}
dumpSignatureData = false
realsig = false
- strictIndentation = None
alwaysInline = None
compilationMode = TcGlobals.CompilationMode.Unset
}
@@ -1255,7 +1252,6 @@ type TcConfig private (data: TcConfigBuilder, validate: bool) =
member _.bufferWidth = data.bufferWidth
member _.fsiMultiAssemblyEmit = data.fsiMultiAssemblyEmit
member _.FxResolver = data.FxResolver
- member _.strictIndentation = data.strictIndentation
member _.alwaysInline =
data.alwaysInline
diff --git a/src/Compiler/Driver/CompilerConfig.fsi b/src/Compiler/Driver/CompilerConfig.fsi
index 9f19b8e59ba..89731f6decc 100644
--- a/src/Compiler/Driver/CompilerConfig.fsi
+++ b/src/Compiler/Driver/CompilerConfig.fsi
@@ -470,8 +470,6 @@ type TcConfigBuilder =
mutable emitDebugInfoInQuotations: bool
- mutable strictIndentation: bool option
-
mutable alwaysInline: bool option
mutable exename: string option
@@ -814,8 +812,6 @@ type TcConfig =
member FxResolver: FxResolver
- member strictIndentation: bool option
-
member alwaysInline: bool
member GetTargetFrameworkDirectories: unit -> string list
diff --git a/src/Compiler/Driver/CompilerDiagnostics.fs b/src/Compiler/Driver/CompilerDiagnostics.fs
index 7cce266b405..5aaf9b70257 100644
--- a/src/Compiler/Driver/CompilerDiagnostics.fs
+++ b/src/Compiler/Driver/CompilerDiagnostics.fs
@@ -1187,7 +1187,8 @@ type Exception with
| Parser.TOKEN_COLON_QMARK -> SR.GetString("Parser.TOKEN.COLON.QMARK")
| Parser.TOKEN_INT32_DOT_DOT -> SR.GetString("Parser.TOKEN.INT32.DOT.DOT")
| Parser.TOKEN_DOT_DOT -> SR.GetString("Parser.TOKEN.DOT.DOT")
- | Parser.TOKEN_DOT_DOT_HAT -> SR.GetString("Parser.TOKEN.DOT.DOT")
+ | Parser.TOKEN_DOT_DOT_HAT -> SR.GetString("Parser.TOKEN.DOT.DOT.HAT")
+ | Parser.TOKEN_DOT_DOT_DOT -> SR.GetString("Parser.TOKEN.DOT.DOT.DOT")
| Parser.TOKEN_QUOTE -> SR.GetString("Parser.TOKEN.QUOTE")
| Parser.TOKEN_STAR -> SR.GetString("Parser.TOKEN.STAR")
| Parser.TOKEN_HIGH_PRECEDENCE_TYAPP -> SR.GetString("Parser.TOKEN.HIGH.PRECEDENCE.TYAPP")
diff --git a/src/Compiler/Driver/CompilerOptions.fs b/src/Compiler/Driver/CompilerOptions.fs
index f54f36fa7f9..48574325813 100644
--- a/src/Compiler/Driver/CompilerOptions.fs
+++ b/src/Compiler/Driver/CompilerOptions.fs
@@ -1200,14 +1200,6 @@ let languageFlags tcConfigB =
CompilerOption("define", tagString, OptionString(defineSymbol tcConfigB), None, Some(FSComp.SR.optsDefine ()))
- CompilerOption(
- "strict-indentation",
- tagNone,
- OptionSwitch(fun switch -> tcConfigB.strictIndentation <- Some(switch = OptionSwitch.On)),
- None,
- Some(FSComp.SR.optsStrictIndentation (formatOptionSwitch (Option.defaultValue false tcConfigB.strictIndentation)))
- )
-
CompilerOption(
"always-inline",
tagNone,
diff --git a/src/Compiler/Driver/GraphChecking/FileContentMapping.fs b/src/Compiler/Driver/GraphChecking/FileContentMapping.fs
index 38ce5a8d8cd..48376289dcc 100644
--- a/src/Compiler/Driver/GraphChecking/FileContentMapping.fs
+++ b/src/Compiler/Driver/GraphChecking/FileContentMapping.fs
@@ -1,4 +1,4 @@
-module internal rec FSharp.Compiler.GraphChecking.FileContentMapping
+module internal rec FSharp.Compiler.GraphChecking.FileContentMapping
open FSharp.Compiler.Syntax
open FSharp.Compiler.SyntaxTreeOps
@@ -127,7 +127,13 @@ let visitSynTypeDefn
match simpleRepr with
| SynTypeDefnSimpleRepr.Union(unionCases = unionCases) -> yield! List.collect visitSynUnionCase unionCases
| SynTypeDefnSimpleRepr.Enum(cases = cases) -> yield! List.collect visitSynEnumCase cases
- | SynTypeDefnSimpleRepr.Record(recordFields = recordFields) -> yield! List.collect visitSynField recordFields
+ | SynTypeDefnSimpleRepr.Record(recordFieldsAndSpreads = fieldsAndSpreads) ->
+ yield!
+ List.collect
+ (function
+ | SynFieldOrSpread.Field field -> visitSynField field
+ | SynFieldOrSpread.Spread spread -> visitSynTypeSpread spread)
+ fieldsAndSpreads
// This is only used in the typed tree
// The parser doesn't construct this
| SynTypeDefnSimpleRepr.General _
@@ -168,7 +174,13 @@ let visitSynTypeDefnSig
match simpleRepr with
| SynTypeDefnSimpleRepr.Union(unionCases = unionCases) -> yield! List.collect visitSynUnionCase unionCases
| SynTypeDefnSimpleRepr.Enum(cases = cases) -> yield! List.collect visitSynEnumCase cases
- | SynTypeDefnSimpleRepr.Record(recordFields = recordFields) -> yield! List.collect visitSynField recordFields
+ | SynTypeDefnSimpleRepr.Record(recordFieldsAndSpreads = fieldsAndSpreads) ->
+ yield!
+ List.collect
+ (function
+ | SynFieldOrSpread.Field field -> visitSynField field
+ | SynFieldOrSpread.Spread spread -> visitSynTypeSpread spread)
+ fieldsAndSpreads
// This is only used in the typed tree
// The parser doesn't construct this
| SynTypeDefnSimpleRepr.General _
@@ -204,6 +216,8 @@ let visitSynValSig (SynValSig(attributes = attributes; synType = synType; synExp
let visitSynField (SynField(attributes = attributes; fieldType = fieldType)) =
visitSynAttributes attributes @ visitSynType fieldType
+let visitSynTypeSpread (SynTypeSpread(ty = ty)) = visitSynType ty
+
let visitSynMemberDefn (md: SynMemberDefn) : FileContentEntry list =
[
match md with
@@ -386,8 +400,19 @@ let visitSynExpr (e: SynExpr) : FileContentEntry list =
| SynExpr.AnonRecd(copyInfo = copyInfo; recordFields = recordFields) ->
let continuations =
match copyInfo with
- | None -> List.map (fun (_, _, e) -> visit e) recordFields
- | Some(cp, _) -> visit cp :: List.map (fun (_, _, e) -> visit e) recordFields
+ | None ->
+ List.map
+ (function
+ | SynExprAnonRecordFieldOrSpread.Field(SynExprAnonRecordField(_, _, e, _), _)
+ | SynExprAnonRecordFieldOrSpread.Spread(spread = SynExprSpread(expr = e)) -> visit e)
+ recordFields
+ | Some(cp, _) ->
+ visit cp
+ :: List.map
+ (function
+ | SynExprAnonRecordFieldOrSpread.Field(SynExprAnonRecordField(_, _, e, _), _)
+ | SynExprAnonRecordFieldOrSpread.Spread(spread = SynExprSpread(expr = e)) -> visit e)
+ recordFields
Continuation.concatenate continuations continuation
| SynExpr.ArrayOrList(exprs = exprs) ->
@@ -396,9 +421,12 @@ let visitSynExpr (e: SynExpr) : FileContentEntry list =
| SynExpr.Record(baseInfo = baseInfo; copyInfo = copyInfo; recordFields = recordFields) ->
let fieldNodes =
[
- for SynExprRecordField(fieldName = (si, _); expr = expr) in recordFields do
- yield! visitSynLongIdent si
- yield! collectFromOption visitSynExpr expr
+ for fieldOrSpread in recordFields do
+ match fieldOrSpread with
+ | SynExprRecordFieldOrSpread.Field(SynExprRecordField(fieldName = (si, _); expr = expr), _) ->
+ yield! visitSynLongIdent si
+ yield! collectFromOption visitSynExpr expr
+ | SynExprRecordFieldOrSpread.Spread(spread = SynExprSpread(expr = expr)) -> yield! visitSynExpr expr
]
match baseInfo, copyInfo with
diff --git a/src/Compiler/Driver/ParseAndCheckInputs.fs b/src/Compiler/Driver/ParseAndCheckInputs.fs
index 92e72d6b89b..1590b9fe458 100644
--- a/src/Compiler/Driver/ParseAndCheckInputs.fs
+++ b/src/Compiler/Driver/ParseAndCheckInputs.fs
@@ -648,7 +648,7 @@ let parseInputStreamAux
// Set up the LexBuffer for the file
let lexbuf =
- UnicodeLexing.StreamReaderAsLexbuf(not tcConfig.compilingFSharpCore, tcConfig.langVersion, tcConfig.strictIndentation, reader)
+ UnicodeLexing.StreamReaderAsLexbuf(not tcConfig.compilingFSharpCore, tcConfig.langVersion, reader)
// Parse the file drawing tokens from the lexbuf
ParseOneInputLexbuf(tcConfig, lexResourceManager, lexbuf, fileName, isLastCompiland, diagnosticsLogger)
@@ -658,7 +658,7 @@ let parseInputSourceTextAux
=
// Set up the LexBuffer for the file
let lexbuf =
- UnicodeLexing.SourceTextAsLexbuf(not tcConfig.compilingFSharpCore, tcConfig.langVersion, tcConfig.strictIndentation, sourceText)
+ UnicodeLexing.SourceTextAsLexbuf(not tcConfig.compilingFSharpCore, tcConfig.langVersion, sourceText)
// Parse the file drawing tokens from the lexbuf
ParseOneInputLexbuf(tcConfig, lexResourceManager, lexbuf, fileName, isLastCompiland, diagnosticsLogger)
@@ -670,7 +670,7 @@ let parseInputFileAux (tcConfig: TcConfig, lexResourceManager, fileName, isLastC
// Set up the LexBuffer for the file
let lexbuf =
- UnicodeLexing.StreamReaderAsLexbuf(not tcConfig.compilingFSharpCore, tcConfig.langVersion, tcConfig.strictIndentation, reader)
+ UnicodeLexing.StreamReaderAsLexbuf(not tcConfig.compilingFSharpCore, tcConfig.langVersion, reader)
// Parse the file drawing tokens from the lexbuf
ParseOneInputLexbuf(tcConfig, lexResourceManager, lexbuf, fileName, isLastCompiland, diagnosticsLogger)
diff --git a/src/Compiler/Driver/ScriptClosure.fs b/src/Compiler/Driver/ScriptClosure.fs
index 7f25c7b826d..a83b49a2a0e 100644
--- a/src/Compiler/Driver/ScriptClosure.fs
+++ b/src/Compiler/Driver/ScriptClosure.fs
@@ -15,7 +15,6 @@ open FSharp.Compiler.CompilerConfig
open FSharp.Compiler.CompilerDiagnostics
open FSharp.Compiler.CompilerImports
open FSharp.Compiler.DependencyManager
-open FSharp.Compiler.Diagnostics
open FSharp.Compiler.DiagnosticsLogger
open FSharp.Compiler.IO
open FSharp.Compiler.CodeAnalysis
@@ -135,7 +134,7 @@ module ScriptPreprocessClosure =
let tcConfig = TcConfig.Create(tcConfigB, false)
let lexbuf =
- UnicodeLexing.SourceTextAsLexbuf(true, tcConfig.langVersion, tcConfig.strictIndentation, sourceText)
+ UnicodeLexing.SourceTextAsLexbuf(true, tcConfig.langVersion, sourceText)
// The root compiland is last in the list of compilands.
let isLastCompiland = (IsScript fileName, tcConfig.target.IsExe)
diff --git a/src/Compiler/Driver/fsc.fs b/src/Compiler/Driver/fsc.fs
index 43157660212..f5aa287b6a7 100644
--- a/src/Compiler/Driver/fsc.fs
+++ b/src/Compiler/Driver/fsc.fs
@@ -1149,6 +1149,7 @@ let main6
referenceAssemblyAttribOpt = referenceAssemblyAttribOpt
referenceAssemblySignatureHash = refAssemblySignatureHash
pathMap = tcConfig.pathMap
+ methodCustomDebugInfoRows = Map.empty
},
ilxMainModule,
normalizeAssemblyRefs
@@ -1180,6 +1181,7 @@ let main6
referenceAssemblyAttribOpt = None
referenceAssemblySignatureHash = None
pathMap = tcConfig.pathMap
+ methodCustomDebugInfoRows = Map.empty
},
ilxMainModule,
normalizeAssemblyRefs
diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt
index c6cbc797da5..68a2764b197 100644
--- a/src/Compiler/FSComp.txt
+++ b/src/Compiler/FSComp.txt
@@ -999,7 +999,7 @@ lexhlpIdentifierReserved,"The identifier '%s' is reserved for future use by F#"
1118,optFailedToInlineValue,"Failed to inline the value '%s' marked 'inline', perhaps because a recursive value was marked 'inline'"
1119,optRecursiveValValue,"Recursive ValValue %s"
lexfltIncorrentIndentationOfIn,"The indentation of this 'in' token is incorrect with respect to the corresponding 'let'"
-lexfltTokenIsOffsideOfContextStartedEarlier,"Unexpected syntax or possible incorrect indentation: this token is offside of context started at position %s. Try indenting this further.\nTo continue using non-conforming indentation, pass the '--strict-indentation-' flag to the compiler, or set the language version to F# 7."
+lexfltTokenIsOffsideOfContextStartedEarlier,"Unexpected syntax or possible incorrect indentation: this token is offside of context started at position %s. Try indenting this further."
lexfltSeparatorTokensOfPatternMatchMisaligned,"The '|' tokens separating rules of this pattern match are misaligned by one column. Consider realigning your code or using further indentation."
lexfltInvalidNestedTypeDefinition,"Nested type definitions are not allowed. Types must be defined at module or namespace level."
lexfltInvalidNestedModule,"Modules cannot be nested inside types. Define modules at module or namespace level."
@@ -1560,7 +1560,6 @@ optsGetLangVersions,"Display the allowed values for language version."
optsSetLangVersion,"Specify language version such as 'latest' or 'preview'."
optsDisableLanguageFeature,"Disable a specific language feature by name."
optsSupportedLangVersions,"Supported language versions:"
-optsStrictIndentation,"Override indentation rules implied by the language version (%s by default)"
optsAlwaysInline,"Always inline 'inline' functions"
nativeResourceFormatError,"Stream does not begin with a null resource and is not in '.RES' format."
nativeResourceHeaderMalformed,"Resource header beginning at offset %s is malformed."
@@ -1606,7 +1605,6 @@ featureNestedCopyAndUpdate,"Nested record field copy-and-update"
featureExtendedStringInterpolation,"Extended string interpolation similar to C# raw string literals."
featureWarningWhenMultipleRecdTypeChoice,"Raises warnings when multiple record type matches were found during name resolution because of overlapping field names."
featureImprovedImpliedArgumentNames,"Improved implied argument names"
-featureStrictIndentation,"Raises errors on incorrect indentation, allows better recovery and analysis during editing"
featureConstraintIntersectionOnFlexibleTypes,"Constraint intersection on flexible types"
featureChkNotTailRecursive,"Raises warnings if a member or function has the 'TailCall' attribute, but is not being used in a tail recursive way."
featureWhileBang,"'while!' expression"
@@ -1822,7 +1820,25 @@ featurePreprocessorElif,"#elif preprocessor directive"
3888,implAttributeMissingFromSignature,"The attribute '%s' is present on '%s' in the implementation but not in the signature, which takes precedence for tooling and consumers. Add the attribute to the signature, to ensure the attribute is not ignored by the compiler."
3889,tastNamespaceAndTypeWithSameNameInAssembly,"The namespace '%s' clashes with the type '%s'."
3890,tcRecursiveInlineNotAllowed,"The value or member '%s' has been marked 'inline' but is part of a recursive binding group. F# does not support recursive 'inline' values. Either remove the 'inline' modifier or refactor the recursion."
+3891,tcGenericAttributesNotSupported,"Generic attribute types are not supported in F#. The type '%s' has type parameters and cannot be used as an attribute."
featureExceptionFieldSerializationSupport,"emit GetObjectData and field-restoring deserialization constructor for exception types"
featureErrorOnMissingSignatureAttribute,"error (rather than warning) when an enforced compiler-semantic attribute is present in the .fs but missing from the .fsi"
+featureNotNullIfNotNull,"honor the 'NotNullIfNotNull' attribute on a method's return value"
+featureDirectDelegateConstruction,"construct delegates that point directly at the target method, avoiding an intermediate closure"
featureAccessProtectedBaseFieldFromClosure,"Access a protected base-class field from a closure inside a member"
featureImprovedImpliedArgumentNamesPartTwo,"Improved implied argument names with partial application"
+3891,tcRecordTypeDefinitionSpreadSourceMustBeRecord,"The source type of a spread into a record type definition must itself be a nominal or anonymous record type."
+3892,tcRecordTypeDefinitionSpreadSourceCannotBeNullable,"The source type of a spread into a record type definition cannot be nullable."
+3893,tcRecordExprSpreadSourceMustBeRecord,"The source expression of a spread into a nominal record expression must have a nominal or anonymous record type."
+3894,tcRecordExprSpreadSourceCannotBeNullable,"The source expression of a spread into a nominal record expression cannot be nullable."
+3895,tcAnonRecordExprSpreadSourceMustBeRecord,"The source expression of a spread into an anonymous record expression must have a nominal or anonymous record type."
+3896,tcAnonRecordExprSpreadSourceCannotBeNullable,"The source expression of a spread into an anonymous record expression cannot be nullable."
+3897,tcRecordTypeDefinitionSpreadFieldShadowsExplicitField,"Spread field '%s' from type '%s' shadows an explicitly declared field with the same name."
+3898,tcRecordExprSpreadFieldShadowsExplicitField,"Spread field '%s' shadows an explicitly declared field with the same name."
+3899,parsMissingSpreadSrcExpr,"Missing spread source expression after '...'."
+3900,parsMissingSpreadSrcTy,"Missing spread source type after '...'."
+3901,tcTypeDefinitionIsCyclicThroughSpreads,"This type definition involves a cyclic reference through a spread."
+3902,parsSpreadNotSupported,"Spreading is not supported in this construct."
+3903,parsSpreadNotSupportedBeforeWith,"Spreading is not supported in this position. Use one of the forms {{ ...expr1; A = expr2 }} or {{ expr1 with A = expr2 }} instead."
+3904,tcRecordExprSpreadWithCannotBeUsedWithSpreads,"Spread expressions and 'with' cannot be used together in the same copy-and-update expression."
+featureRecordSpreads,"record type and expression spreads"
diff --git a/src/Compiler/FSStrings.resx b/src/Compiler/FSStrings.resx
index 698881678c2..ef058b350c1 100644
--- a/src/Compiler/FSStrings.resx
+++ b/src/Compiler/FSStrings.resx
@@ -371,10 +371,10 @@
symbol '>|}'
-
+
symbol '@>|}' or '@@>|}'
-
+
symbol '>|]'
@@ -1179,4 +1179,7 @@
No constructors are available for the type '{0}'
+
+ symbol '...'
+
\ No newline at end of file
diff --git a/src/Compiler/FSharp.Compiler.Service.fsproj b/src/Compiler/FSharp.Compiler.Service.fsproj
index 5510af6b3f6..bdaf5999a16 100644
--- a/src/Compiler/FSharp.Compiler.Service.fsproj
+++ b/src/Compiler/FSharp.Compiler.Service.fsproj
@@ -236,6 +236,8 @@
+
+
@@ -306,6 +308,8 @@
SyntaxTree\LexHelpers.fs
+
+
SyntaxTree\FsLexOutput\pplex.fsi
@@ -326,6 +330,7 @@
+
@@ -402,6 +407,7 @@
+
@@ -418,6 +424,7 @@
+
@@ -621,17 +628,17 @@
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/src/Compiler/Facilities/LanguageFeatures.fs b/src/Compiler/Facilities/LanguageFeatures.fs
index 9ecc56472c6..c4f81878f8d 100644
--- a/src/Compiler/Facilities/LanguageFeatures.fs
+++ b/src/Compiler/Facilities/LanguageFeatures.fs
@@ -20,7 +20,6 @@ type LanguageFeature =
| WildCardInForLoop
| RelaxWhitespace
| RelaxWhitespace2
- | StrictIndentation
| NameOf
| ImplicitYield
| OpenTypeDeclaration
@@ -110,8 +109,11 @@ type LanguageFeature =
| PreprocessorElif
| ExceptionFieldSerializationSupport
| ErrorOnMissingSignatureAttribute
+ | NotNullIfNotNull
+ | DirectDelegateConstruction
| AccessProtectedBaseFieldFromClosure
| ImprovedImpliedArgumentNamesPartTwo
+ | RecordSpreads
/// LanguageVersion management
type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array) =
@@ -214,7 +216,6 @@ type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array)
LanguageFeature.DiagnosticForObjInference, languageVersion80
LanguageFeature.WarningWhenTailRecAttributeButNonTailRecUsage, languageVersion80
LanguageFeature.StaticLetInRecordsDusEmptyTypes, languageVersion80
- LanguageFeature.StrictIndentation, languageVersion80
LanguageFeature.ConstraintIntersectionOnFlexibleTypes, languageVersion80
LanguageFeature.WhileBang, languageVersion80
LanguageFeature.ExtendedFixedBindings, languageVersion80
@@ -256,6 +257,7 @@ type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array)
LanguageFeature.WarnWhenFunctionValueUsedAsInterpolatedStringArg, languageVersion110
LanguageFeature.PreprocessorElif, languageVersion110
LanguageFeature.ExceptionFieldSerializationSupport, languageVersion110
+ LanguageFeature.NotNullIfNotNull, languageVersion110
LanguageFeature.ImprovedImpliedArgumentNamesPartTwo, languageVersion110
// Difference between languageVersion110 and preview - 11.0 gets turned on automatically by picking a preview .NET 11 SDK
@@ -266,7 +268,9 @@ type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array)
LanguageFeature.MethodOverloadsCache, previewVersion // Performance optimization for overload resolution
LanguageFeature.ImplicitDIMCoverage, languageVersion110
LanguageFeature.ErrorOnMissingSignatureAttribute, previewVersion // Opt-in: turn FS3888 from warning into error
+ LanguageFeature.DirectDelegateConstruction, previewVersion
LanguageFeature.AccessProtectedBaseFieldFromClosure, previewVersion // #5302: read a protected base field from a closure
+ LanguageFeature.RecordSpreads, previewVersion
]
static let defaultLanguageVersion = LanguageVersion("default")
@@ -421,7 +425,6 @@ type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array)
| LanguageFeature.DiagnosticForObjInference -> FSComp.SR.featureInformationalObjInferenceDiagnostic ()
| LanguageFeature.StaticLetInRecordsDusEmptyTypes -> FSComp.SR.featureStaticLetInRecordsDusEmptyTypes ()
- | LanguageFeature.StrictIndentation -> FSComp.SR.featureStrictIndentation ()
| LanguageFeature.ConstraintIntersectionOnFlexibleTypes -> FSComp.SR.featureConstraintIntersectionOnFlexibleTypes ()
| LanguageFeature.WarningWhenTailRecAttributeButNonTailRecUsage -> FSComp.SR.featureChkNotTailRecursive ()
| LanguageFeature.UnmanagedConstraintCsharpInterop -> FSComp.SR.featureUnmanagedConstraintCsharpInterop ()
@@ -463,8 +466,11 @@ type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array)
| LanguageFeature.PreprocessorElif -> FSComp.SR.featurePreprocessorElif ()
| LanguageFeature.ExceptionFieldSerializationSupport -> FSComp.SR.featureExceptionFieldSerializationSupport ()
| LanguageFeature.ErrorOnMissingSignatureAttribute -> FSComp.SR.featureErrorOnMissingSignatureAttribute ()
+ | LanguageFeature.NotNullIfNotNull -> FSComp.SR.featureNotNullIfNotNull ()
+ | LanguageFeature.DirectDelegateConstruction -> FSComp.SR.featureDirectDelegateConstruction ()
| LanguageFeature.AccessProtectedBaseFieldFromClosure -> FSComp.SR.featureAccessProtectedBaseFieldFromClosure ()
| LanguageFeature.ImprovedImpliedArgumentNamesPartTwo -> FSComp.SR.featureImprovedImpliedArgumentNamesPartTwo ()
+ | LanguageFeature.RecordSpreads -> FSComp.SR.featureRecordSpreads ()
/// Get a version string associated with the given feature.
static member GetFeatureVersionString feature =
diff --git a/src/Compiler/Facilities/LanguageFeatures.fsi b/src/Compiler/Facilities/LanguageFeatures.fsi
index 4aa85a42224..d0b97987137 100644
--- a/src/Compiler/Facilities/LanguageFeatures.fsi
+++ b/src/Compiler/Facilities/LanguageFeatures.fsi
@@ -10,7 +10,6 @@ type LanguageFeature =
| WildCardInForLoop
| RelaxWhitespace
| RelaxWhitespace2
- | StrictIndentation
| NameOf
| ImplicitYield
| OpenTypeDeclaration
@@ -101,8 +100,11 @@ type LanguageFeature =
| PreprocessorElif
| ExceptionFieldSerializationSupport
| ErrorOnMissingSignatureAttribute
+ | NotNullIfNotNull
+ | DirectDelegateConstruction
| AccessProtectedBaseFieldFromClosure
| ImprovedImpliedArgumentNamesPartTwo
+ | RecordSpreads
/// LanguageVersion management
type LanguageVersion =
diff --git a/src/Compiler/Facilities/prim-lexing.fs b/src/Compiler/Facilities/prim-lexing.fs
index cfde35d5a77..21b93b12880 100644
--- a/src/Compiler/Facilities/prim-lexing.fs
+++ b/src/Compiler/Facilities/prim-lexing.fs
@@ -242,8 +242,7 @@ type internal Position =
type internal LexBufferFiller<'Char> = LexBuffer<'Char> -> unit
-and [] internal LexBuffer<'Char>
- (filler: LexBufferFiller<'Char>, reportLibraryOnlyFeatures: bool, langVersion: LanguageVersion, strictIndentation: bool option) =
+and [] internal LexBuffer<'Char>(filler: LexBufferFiller<'Char>, reportLibraryOnlyFeatures: bool, langVersion: LanguageVersion) =
let context = Dictionary(1)
let mutable buffer = [||]
/// number of valid characters beyond bufferScanStart.
@@ -344,14 +343,10 @@ and [] internal LexBuffer<'Char>
member _.SupportsFeature featureId = langVersion.SupportsFeature featureId
- member _.StrictIndentation = strictIndentation
-
member _.CheckLanguageFeatureAndRecover featureId range =
FSharp.Compiler.DiagnosticsLogger.checkLanguageFeatureAndRecover langVersion featureId range
- static member FromFunction
- (reportLibraryOnlyFeatures, langVersion, strictIndentation, f: 'Char[] * int * int -> int)
- : LexBuffer<'Char> =
+ static member FromFunction(reportLibraryOnlyFeatures, langVersion, f: 'Char[] * int * int -> int) : LexBuffer<'Char> =
let extension = Array.zeroCreate 4096
let filler (lexBuffer: LexBuffer<'Char>) =
@@ -360,35 +355,34 @@ and [] internal LexBuffer<'Char>
Array.blit extension 0 lexBuffer.Buffer lexBuffer.BufferScanPos n
lexBuffer.BufferMaxScanLength <- lexBuffer.BufferScanLength + n
- new LexBuffer<'Char>(filler, reportLibraryOnlyFeatures, langVersion, strictIndentation)
+ new LexBuffer<'Char>(filler, reportLibraryOnlyFeatures, langVersion)
// Important: This method takes ownership of the array
- static member FromArrayNoCopy(reportLibraryOnlyFeatures, langVersion, strictIndentation, buffer: 'Char[]) : LexBuffer<'Char> =
+ static member FromArrayNoCopy(reportLibraryOnlyFeatures, langVersion, buffer: 'Char[]) : LexBuffer<'Char> =
let lexBuffer =
- new LexBuffer<'Char>((fun _ -> ()), reportLibraryOnlyFeatures, langVersion, strictIndentation)
+ new LexBuffer<'Char>((fun _ -> ()), reportLibraryOnlyFeatures, langVersion)
lexBuffer.Buffer <- buffer
lexBuffer.BufferMaxScanLength <- buffer.Length
lexBuffer
// Important: this method does copy the array
- static member FromArray(reportLibraryOnlyFeatures, langVersion, strictIndentation, s: 'Char[]) : LexBuffer<'Char> =
+ static member FromArray(reportLibraryOnlyFeatures, langVersion, s: 'Char[]) : LexBuffer<'Char> =
let buffer = Array.copy s
- LexBuffer<'Char>.FromArrayNoCopy(reportLibraryOnlyFeatures, langVersion, strictIndentation, buffer)
+ LexBuffer<'Char>.FromArrayNoCopy(reportLibraryOnlyFeatures, langVersion, buffer)
// Important: This method takes ownership of the array
- static member FromChars(reportLibraryOnlyFeatures, langVersion, strictIndentation, arr: char[]) =
- LexBuffer.FromArrayNoCopy(reportLibraryOnlyFeatures, langVersion, strictIndentation, arr)
+ static member FromChars(reportLibraryOnlyFeatures, langVersion, arr: char[]) =
+ LexBuffer.FromArrayNoCopy(reportLibraryOnlyFeatures, langVersion, arr)
- static member FromSourceText(reportLibraryOnlyFeatures, langVersion, strictIndentation, sourceText: ISourceText) =
+ static member FromSourceText(reportLibraryOnlyFeatures, langVersion, sourceText: ISourceText) =
let mutable currentSourceIndex = 0
LexBuffer
.FromFunction(
reportLibraryOnlyFeatures,
langVersion,
- strictIndentation,
fun (chars, start, length) ->
let lengthToCopy =
if currentSourceIndex + length <= sourceText.Length then
diff --git a/src/Compiler/Facilities/prim-lexing.fsi b/src/Compiler/Facilities/prim-lexing.fsi
index bcb60fc4977..f74d4baa2df 100644
--- a/src/Compiler/Facilities/prim-lexing.fsi
+++ b/src/Compiler/Facilities/prim-lexing.fsi
@@ -146,29 +146,21 @@ type internal LexBuffer<'Char> =
/// True if the specified language feature is supported.
member SupportsFeature: LanguageFeature -> bool
- member StrictIndentation: bool option
-
/// Logs a recoverable error if a language feature is unsupported, at the specified range.
member CheckLanguageFeatureAndRecover: LanguageFeature -> range -> unit
/// Create a lex buffer suitable for Unicode lexing that reads characters from the given array.
/// Important: does take ownership of the array.
- static member FromChars:
- reportLibraryOnlyFeatures: bool * langVersion: LanguageVersion * strictIndentation: bool option * char[] ->
- LexBuffer
+ static member FromChars: reportLibraryOnlyFeatures: bool * langVersion: LanguageVersion * char[] -> LexBuffer
/// Create a lex buffer that reads character or byte inputs by using the given function.
static member FromFunction:
- reportLibraryOnlyFeatures: bool *
- langVersion: LanguageVersion *
- strictIndentation: bool option *
- ('Char[] * int * int -> int) ->
+ reportLibraryOnlyFeatures: bool * langVersion: LanguageVersion * ('Char[] * int * int -> int) ->
LexBuffer<'Char>
/// Create a lex buffer backed by source text.
static member FromSourceText:
- reportLibraryOnlyFeatures: bool * langVersion: LanguageVersion * strictIndentation: bool option * ISourceText ->
- LexBuffer
+ reportLibraryOnlyFeatures: bool * langVersion: LanguageVersion * ISourceText -> LexBuffer
/// The type of tables for an unicode lexer generated by fslex.exe.
[]
diff --git a/src/Compiler/Interactive/fsi.fs b/src/Compiler/Interactive/fsi.fs
index fcb93b6c985..500045c73f7 100644
--- a/src/Compiler/Interactive/fsi.fs
+++ b/src/Compiler/Interactive/fsi.fs
@@ -1941,6 +1941,7 @@ type internal FsiDynamicCompiler
referenceAssemblyAttribOpt = None
referenceAssemblySignatureHash = None
pathMap = tcConfig.pathMap
+ methodCustomDebugInfoRows = Map.empty
}
let assemblyBytes, pdbBytes = WriteILBinaryInMemory(opts, ilxMainModule, id)
@@ -3590,7 +3591,6 @@ type FsiStdinLexerProvider
UnicodeLexing.FunctionAsLexbuf(
true,
tcConfigB.langVersion,
- tcConfigB.strictIndentation,
(fun (buf: char[], start, len) ->
//fprintf fsiConsoleOutput.Out "Calling ReadLine\n"
let inputOption =
@@ -3669,15 +3669,13 @@ type FsiStdinLexerProvider
// Create a new lexer to read an "included" script file
member _.CreateIncludedScriptLexer(sourceFileName, reader, diagnosticsLogger) =
- let lexbuf =
- UnicodeLexing.StreamReaderAsLexbuf(true, tcConfigB.langVersion, tcConfigB.strictIndentation, reader)
+ let lexbuf = UnicodeLexing.StreamReaderAsLexbuf(true, tcConfigB.langVersion, reader)
CreateLexerForLexBuffer(sourceFileName, lexbuf, diagnosticsLogger)
// Create a new lexer to read a string
member _.CreateStringLexer(sourceFileName, source, diagnosticsLogger) =
- let lexbuf =
- UnicodeLexing.StringAsLexbuf(true, tcConfigB.langVersion, tcConfigB.strictIndentation, source)
+ let lexbuf = UnicodeLexing.StringAsLexbuf(true, tcConfigB.langVersion, source)
CreateLexerForLexBuffer(sourceFileName, lexbuf, diagnosticsLogger)
@@ -3798,7 +3796,7 @@ type FsiInteractionProcessor
let runhDirective diagnosticsLogger ctok istate source =
let lexbuf =
- UnicodeLexing.StringAsLexbuf(true, tcConfigB.langVersion, tcConfigB.strictIndentation, $"<@@ {source} @@>")
+ UnicodeLexing.StringAsLexbuf(true, tcConfigB.langVersion, $"<@@ {source} @@>")
let tokenizer =
fsiStdinLexerProvider.CreateBufferLexer("hdummy.fsx", lexbuf, diagnosticsLogger)
@@ -4361,8 +4359,7 @@ type FsiInteractionProcessor
use _ = UseDiagnosticsLogger diagnosticsLogger
use _scope = SetCurrentUICultureForThread fsiOptions.FsiLCID
- let lexbuf =
- UnicodeLexing.StringAsLexbuf(true, tcConfigB.langVersion, tcConfigB.strictIndentation, sourceText)
+ let lexbuf = UnicodeLexing.StringAsLexbuf(true, tcConfigB.langVersion, sourceText)
let tokenizer =
fsiStdinLexerProvider.CreateBufferLexer(scriptFileName, lexbuf, diagnosticsLogger)
@@ -4383,8 +4380,7 @@ type FsiInteractionProcessor
use _unwind2 = UseDiagnosticsLogger diagnosticsLogger
use _scope = SetCurrentUICultureForThread fsiOptions.FsiLCID
- let lexbuf =
- UnicodeLexing.StringAsLexbuf(true, tcConfigB.langVersion, tcConfigB.strictIndentation, sourceText)
+ let lexbuf = UnicodeLexing.StringAsLexbuf(true, tcConfigB.langVersion, sourceText)
let tokenizer =
fsiStdinLexerProvider.CreateBufferLexer(scriptFileName, lexbuf, diagnosticsLogger)
diff --git a/src/Compiler/Optimize/DelegateForwarding.fs b/src/Compiler/Optimize/DelegateForwarding.fs
new file mode 100644
index 00000000000..efa0fdfe9b3
--- /dev/null
+++ b/src/Compiler/Optimize/DelegateForwarding.fs
@@ -0,0 +1,295 @@
+// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
+
+/// Recognition of delegate constructions whose Invoke body is a transparent forwarding call to a known
+/// method, shared by the optimizer (which preserves the call from inlining) and the ILX generator (which
+/// points the delegate directly at the target). The 'exprHasEffect' parameter is Optimizer.ExprHasEffect;
+/// it is passed in because this file compiles before the optimizer.
+module internal FSharp.Compiler.DelegateForwarding
+
+open Internal.Utilities.Collections
+
+open FSharp.Compiler.AbstractIL.IL
+open FSharp.Compiler.Text
+open FSharp.Compiler.TcGlobals
+open FSharp.Compiler.TypedTree
+open FSharp.Compiler.TypedTreeBasics
+open FSharp.Compiler.TypedTreeOps
+
+/// A delegate target that can potentially be forwarded to directly, without an intermediate closure
+[]
+type DirectDelegateForwardingTargetCandidate =
+ /// A known F# value: a module-level function or a member
+ | FSharpVal of vref: ValRef * valUseFlags: ValUseFlag * tyargs: TypeInst * leadingArgs: Expr list
+ /// A direct IL method call (e.g. a BCL method)
+ | ILMethod of
+ isVirtual: bool *
+ isStruct: bool *
+ isCtor: bool *
+ valUseFlags: ValUseFlag *
+ ilMethRef: ILMethodRef *
+ enclTypeInst: TypeInst *
+ methInst: TypeInst *
+ leadingArgs: Expr list
+ | Other
+
+let private isUnitValue e =
+ match stripDebugPoints e with
+ | Expr.Const(Const.Unit, _, _) -> true
+ | _ -> false
+
+// Mirror the code generator's arity-based de-tupling (a tupled argument group is one tuple node in the
+// call but separate IL parameters in the compiled target) so the match sees the flattened argument list.
+// The group count must equal the target's arity exactly: fewer is a partial application, more an
+// over-application whose trailing arguments are consumed by the target's *result*, and a target without
+// arity information has no compiled method to point at.
+let private tryFlattenTupledArgs (vref: ValRef) (args: Expr list) =
+ let arities = (arityOfVal vref.Deref).AritiesOfArgs
+
+ if arities.Length <> args.Length then
+ None
+ else
+ (arities, args)
+ ||> List.map2 (fun arity arg ->
+ match stripDebugPoints arg with
+ | Expr.Op(TOp.Tuple _, _, elems, _) when arity >= 2 && elems.Length = arity -> elems
+ | _ -> [ arg ])
+ |> List.concat
+ |> Some
+
+let rec private resolveAliases (aliases: ValMap) e =
+ let e = stripDebugPoints e
+
+ match e with
+ | Expr.Val(vref, _, _) ->
+ match aliases.TryFind vref.Deref with
+ | Some e2 -> resolveAliases aliases e2
+ | None -> e
+ | _ -> e
+
+// Trailing arguments must be the delegate's Invoke parameters, verbatim and in order; the leading rest
+// (e.g. an instance receiver) is resolved and returned for the caller to check and emit.
+let private matchForwarding g (aliases: ValMap) (invokeParams: Val list) (args: Expr list) =
+ let args = args |> List.map (resolveAliases aliases)
+
+ // Drop the elided unit argument when the Invoke takes no parameters.
+ let args =
+ match List.tryLast args with
+ | Some last when List.isEmpty invokeParams && isUnitValue last -> List.truncate (args.Length - 1) args
+ | _ -> args
+
+ let numLeading = args.Length - invokeParams.Length
+
+ if numLeading >= 0 then
+ let leadingArgs, forwardedArgs = List.splitAt numLeading args
+
+ if
+ List.forall2
+ (fun (a: Expr) (tv: Val) ->
+ match a with
+ | Expr.Val(avref, _, _) -> valRefEq g avref (mkLocalValRef tv)
+ | _ -> false)
+ forwardedArgs
+ invokeParams
+ then
+ // A struct receiver arrives by address; recover the value so the emit can box it as the
+ // Target (invocation reaches 'this' through the runtime's unboxing stub).
+ let leadingArgs =
+ leadingArgs
+ |> List.map (fun a ->
+ match a with
+ | Expr.Op(TOp.LValueOp(LAddrOf _, vref), _, _, m) -> resolveAliases aliases (exprForValRef m vref)
+ | _ -> a)
+
+ Some leadingArgs
+ else
+ None
+ else
+ None
+
+// Peel the wrappers the elaborator and BuildNewDelegateExpr leave around the forwarding call: effect-free
+// let-bindings, applications of let-wrapped or immediate lambdas (method-group coercions, the shells of
+// curried member calls), and curried application nesting. The optimizer reduces these only while already
+// making inlining decisions - too late for a recognizer that must precede them - so peel by aliasing:
+// each bound value maps to the expression flowing into it, resolved when the arguments are matched.
+// Anything else is left in place and fails the match, conservatively keeping the closure.
+let rec private stripToForwardingCall exprHasEffect g (aliases: ValMap) expr =
+ match stripDebugPoints expr with
+ | Expr.Let(TBind(v, rhs, _), inner, _, _) when not (exprHasEffect g rhs) ->
+ stripToForwardingCall exprHasEffect g (aliases.Add v rhs) inner
+ | Expr.App(f, fty, tyargs, args, m) as app ->
+ match stripDebugPoints f with
+ | Expr.Let(TBind(v, rhs, _), f2, _, _) when not (exprHasEffect g rhs) ->
+ stripToForwardingCall exprHasEffect g (aliases.Add v rhs) (Expr.App(f2, fty, tyargs, args, m))
+ | Expr.Lambda(_, None, None, [ v ], body, _, _) when List.isEmpty tyargs ->
+ match args with
+ | a :: rest when not (exprHasEffect g a) ->
+ let aliases = aliases.Add v a
+
+ match rest with
+ | [] -> stripToForwardingCall exprHasEffect g aliases body
+ | _ -> stripToForwardingCall exprHasEffect g aliases (Expr.App(body, tyOfExpr g body, [], rest, m))
+ | _ -> app, aliases
+ | Expr.App(f2, f2ty, tyargs2, args2, _) when List.isEmpty tyargs ->
+ stripToForwardingCall exprHasEffect g aliases (Expr.App(f2, f2ty, tyargs2, args2 @ args, m))
+ | _ -> app, aliases
+ | e -> e, aliases
+
+let classifyForwardingTarget exprHasEffect g (invokeParams: Val list) expr =
+ let call, aliases = stripToForwardingCall exprHasEffect g ValMap.Empty expr
+
+ match call with
+ | Expr.App(f, _, tyargs, args, _) ->
+ match stripDebugPoints f with
+ | Expr.Val(vref, valUseFlags, _) ->
+ match
+ tryFlattenTupledArgs vref args
+ |> Option.bind (matchForwarding g aliases invokeParams)
+ with
+ | Some leadingArgs -> DirectDelegateForwardingTargetCandidate.FSharpVal(vref, valUseFlags, tyargs, leadingArgs)
+ | None -> DirectDelegateForwardingTargetCandidate.Other
+ | _ -> DirectDelegateForwardingTargetCandidate.Other
+ | Expr.Op(TOp.ILCall(isVirtual, _, isStruct, isCtor, valUseFlag, _, _, ilMethRef, enclTypeInst, methInst, _), _, args, _) ->
+ match matchForwarding g aliases invokeParams args with
+ | Some leadingArgs ->
+ DirectDelegateForwardingTargetCandidate.ILMethod(
+ isVirtual,
+ isStruct,
+ isCtor,
+ valUseFlag,
+ ilMethRef,
+ enclTypeInst,
+ methInst,
+ leadingArgs
+ )
+ | None -> DirectDelegateForwardingTargetCandidate.Other
+ | _ -> DirectDelegateForwardingTargetCandidate.Other
+
+/// At most one leading argument can become the delegate's Target: the receiver of an instance target, or
+/// the first parameter of a static one via the CLR's "closed over the first argument" delegate form
+/// (extension-member receivers, one-argument partial applications). More has no closed form.
+let private receiverShapeOk (leadingArgs: Expr list) takesInstanceArg =
+ if takesInstanceArg then
+ match leadingArgs with
+ | [ _ ] -> true
+ | _ -> false
+ else
+ match leadingArgs with
+ | []
+ | [ _ ] -> true
+ | _ -> false
+
+let private staticLeadingArgIsRefType g takesInstanceArg (leadingArgs: Expr list) =
+ match leadingArgs with
+ | [ recv ] when not takesInstanceArg -> isRefTy g (tyOfExpr g recv)
+ | _ -> true
+
+let private receiverNotByref g (leadingArgs: Expr list) =
+ match leadingArgs with
+ | [ recv ] -> not (isByrefTy g (tyOfExpr g recv))
+ | _ -> true
+
+let private receiverNotTypar g (leadingArgs: Expr list) =
+ match leadingArgs with
+ | [ recv ] -> not (isTyparTy g (tyOfExpr g recv))
+ | _ -> true
+
+let private receiverNotMutableStruct g takesInstanceArg (leadingArgs: Expr list) =
+ match leadingArgs with
+ | [ recv ] when takesInstanceArg ->
+ let ty = tyOfExpr g recv
+ not (isStructTy g ty) || isRecdOrStructTyReadOnly g Range.range0 ty
+ | _ -> true
+
+/// The receiver is evaluated once at the construction site rather than on every Invoke, which is only
+/// unobservable when it is effect-free; and it must not reference the Invoke parameters, which exist
+/// only inside the delegee.
+let private receiverBindable exprHasEffect g (invokeParams: Val list) (leadingArgs: Expr list) =
+ match leadingArgs with
+ | [ recv ] ->
+ let recvFreeLocals = (freeInExpr CollectLocals recv).FreeLocals
+
+ not (exprHasEffect g recv)
+ && (not (invokeParams |> List.exists (fun tv -> Zset.contains tv recvFreeLocals)))
+ | _ -> true
+
+/// Returns the virtual-call and instance-receiver facts derived from the member call info when the
+/// target is directly bindable. Witnesses are passed in: computing them needs the IlxGen environment.
+let fsharpValDirectlyBindable
+ exprHasEffect
+ g
+ (invokeParams: Val list)
+ (leadingArgs: Expr list)
+ (vrefM: ValRef)
+ (valUseFlags: ValUseFlag)
+ hasWitnesses
+ =
+ let _, virtualCall, newobj, isSuperInit, isSelfInit, takesInstanceArg, _, _ =
+ GetMemberCallInfo g (vrefM, valUseFlags)
+
+ if
+ not hasWitnesses
+ && not newobj
+ && not isSuperInit
+ && not isSelfInit
+ && not valUseFlags.IsVSlotDirectCall
+ && receiverShapeOk leadingArgs takesInstanceArg
+ && receiverBindable exprHasEffect g invokeParams leadingArgs
+ && staticLeadingArgIsRefType g takesInstanceArg leadingArgs
+ && receiverNotByref g leadingArgs
+ && receiverNotTypar g leadingArgs
+ && receiverNotMutableStruct g takesInstanceArg leadingArgs
+ then
+ ValueSome(virtualCall, takesInstanceArg)
+ else
+ ValueNone
+
+let ilMethodDirectlyBindable
+ exprHasEffect
+ g
+ (invokeParams: Val list)
+ (leadingArgs: Expr list)
+ (ilMethRef: ILMethodRef)
+ (valUseFlag: ValUseFlag)
+ isCtor
+ =
+ let takesInstanceArg = ilMethRef.CallingConv.IsInstance
+
+ not isCtor
+ && not valUseFlag.IsVSlotDirectCall
+ && not valUseFlag.IsPossibleConstrainedCall
+ && receiverShapeOk leadingArgs takesInstanceArg
+ && receiverBindable exprHasEffect g invokeParams leadingArgs
+ && staticLeadingArgIsRefType g takesInstanceArg leadingArgs
+ && receiverNotByref g leadingArgs
+ && receiverNotTypar g leadingArgs
+ && receiverNotMutableStruct g takesInstanceArg leadingArgs
+
+/// Residual IL compatibility check; the type checker verified the call and the forwarding match pinned
+/// the shape. Parameter types are deliberately not compared - value types are exact by construction and
+/// reference types may use the CLR's contravariant delegate relaxation - only their count, minus any
+/// leading formals consumed by a bound Target. The return type must match exactly for a non-generic
+/// target (the CLR does not relax e.g. 'void' against 'Unit'); a generic target's return is written in
+/// type variables, where no exact comparison is meaningful.
+let signatureMatches
+ numBoundLeadingFormals
+ (numDelegeeParams: int)
+ (ilDelegeeRetTy: ILType)
+ (ilEnclArgTys: ILType list)
+ (ilMethArgTys: ILType list)
+ (targetMspec: ILMethodSpec)
+ =
+ let arityMatches =
+ targetMspec.FormalArgTypes.Length - numBoundLeadingFormals = numDelegeeParams
+
+ let returnMatches =
+ if List.isEmpty ilEnclArgTys && List.isEmpty ilMethArgTys then
+ ilDelegeeRetTy = targetMspec.FormalReturnType
+ else
+ true
+
+ arityMatches && returnMatches
+
+let receiverInfo (leadingArgs: Expr list) virtualCall isInstanceReceiver =
+ match leadingArgs with
+ | [ recv ] -> Some(recv, virtualCall, isInstanceReceiver)
+ | _ -> None
diff --git a/src/Compiler/Optimize/Optimizer.fs b/src/Compiler/Optimize/Optimizer.fs
index 4748685287d..a6b21b577eb 100644
--- a/src/Compiler/Optimize/Optimizer.fs
+++ b/src/Compiler/Optimize/Optimizer.fs
@@ -12,6 +12,7 @@ open FSharp.Compiler
open FSharp.Compiler.AbstractIL.IL
open FSharp.Compiler.AttributeChecking
open FSharp.Compiler.CompilerGlobalState
+open FSharp.Compiler.DelegateForwarding
open FSharp.Compiler.DiagnosticsLogger
open FSharp.Compiler.Text.Range
open FSharp.Compiler.Syntax.PrettyNaming
@@ -1707,7 +1708,7 @@ and OpHasEffect context g m op tyargs =
| TOp.ExnFieldSet _
| TOp.Coerce
| TOp.Reraise
- | TOp.IntegerForLoop _
+ | TOp.IntegerForLoop _
| TOp.While _
| TOp.TryWith _ (* conservative *)
| TOp.TryFinally _ (* conservative *)
@@ -1722,6 +1723,43 @@ and OpHasEffect context g m op tyargs =
let effectContextOf (cenv: cenv) =
if cenv.optimizing then EffectContext.Emit else EffectContext.InlineBody
+/// Prevent the optimizer from inlining a recognized direct-delegate forwarding target into the delegate
+/// body: inlining would dissolve the call before IlxGen can point the delegate at it, making the emitted
+/// form depend on the target's size (locally, and through a referenced assembly's optimization data).
+/// Mandatory inlining of 'inline' values takes precedence via OptimizeVal.
+let AddDirectDelegateTargetToDontInlineSet cenv env (slotsig: SlotSig) tmvs body m =
+ let g = cenv.g
+
+ if
+ g.langVersion.SupportsFeature Features.LanguageFeature.DirectDelegateConstruction
+ && cenv.optimizing
+ && cenv.settings.InlineLambdas
+ then
+ let exprHasEffect = ExprHasEffect (effectContextOf cenv)
+
+ // Normalize the elided unit parameter of a zero-parameter Invoke (e.g. System.Action) exactly as
+ // IlxGen will before it runs the recognizer
+ let tmvs, body =
+ if slotsig.FormalParams |> List.forall List.isEmpty then
+ BindUnitVars g (tmvs, [], body)
+ else
+ tmvs, body
+
+ match classifyForwardingTarget exprHasEffect g tmvs body with
+ | DirectDelegateForwardingTargetCandidate.FSharpVal(vref, valUseFlags, _, leadingArgs) when
+ // ValReprInfo.IsSome mirrors IlxGen's Method-storage requirement. Witnesses are not knowable
+ // here; over-suppressing a witness-requiring target only costs an inline in a closure body.
+ vref.ValReprInfo.IsSome
+ && (fsharpValDirectlyBindable exprHasEffect g tmvs leadingArgs vref valUseFlags false)
+ .IsSome
+ ->
+ match (GetInfoForVal cenv env m vref).ValExprInfo with
+ | StripLambdaValue(lambdaId, _, _, _, _) ->
+ { env with dontInline = Map.add lambdaId [] env.dontInline }
+ | _ -> env
+ | _ -> env
+ else
+ env
let TryEliminateBinding cenv _env bind e2 _m =
let g = cenv.g
@@ -2441,11 +2479,16 @@ let rec OptimizeExpr cenv (env: IncrementalOptimizationEnv) expr =
MightMakeCriticalTailcall=false
Info=UnknownValue }
- | Expr.Obj (_, ty, basev, createExpr, overrides, iimpls, m) ->
- match expr with
- | NewDelegateExpr g (lambdaId, vsl, body, _, remake) ->
+ | Expr.Obj (_, ty, basev, createExpr, overrides, iimpls, m) ->
+ match expr with
+ | NewDelegateExpr g (lambdaId, vsl, body, _, remake) ->
+ let env =
+ match overrides with
+ | [ TObjExprMethod(slotsig, _, _, _, _, mMeth) ] ->
+ AddDirectDelegateTargetToDontInlineSet cenv env slotsig vsl body mMeth
+ | _ -> env
OptimizeNewDelegateExpr cenv env (lambdaId, vsl, body, remake)
- | _ ->
+ | _ ->
OptimizeObjectExpr cenv env (ty, basev, createExpr, overrides, iimpls, m)
| Expr.Op (op, tyargs, args, m) ->
@@ -2579,19 +2622,7 @@ and MakeOptimizedSystemStringConcatCall cenv env m args =
let args = optimizeArgs args []
- let expr =
- match args with
- | [ arg ] ->
- arg
- | [ arg1; arg2 ] ->
- mkStaticCall_String_Concat2 g m arg1 arg2
- | [ arg1; arg2; arg3 ] ->
- mkStaticCall_String_Concat3 g m arg1 arg2 arg3
- | [ arg1; arg2; arg3; arg4 ] ->
- mkStaticCall_String_Concat4 g m arg1 arg2 arg3 arg4
- | args ->
- let arg = mkArray (g.string_ty, args, m)
- mkStaticCall_String_Concat_Array g m arg
+ let expr = mkStringConcat (g, m, args)
match expr with
| Expr.Op(TOp.ILCall(_, _, _, _, _, _, _, ilMethRef, _, _, _) as op, tyargs, args, m)
diff --git a/src/Compiler/Service/FSharpCheckerResults.fs b/src/Compiler/Service/FSharpCheckerResults.fs
index c7b36f720e2..3d029caa33a 100644
--- a/src/Compiler/Service/FSharpCheckerResults.fs
+++ b/src/Compiler/Service/FSharpCheckerResults.fs
@@ -1567,11 +1567,16 @@ type internal TypeCheckInfo
allSymbols: unit -> AssemblySymbol list,
options: FSharpCodeCompletionOptions
) =
+ let isSpread =
+ FindFirstNonWhitespacePosition lineStr (colAtEndOfNamesAndResidue - 1)
+ |> Option.exists (fun i ->
+ (i > 2 && lineStr[i - 3] <> '.' || i = 2)
+ && lineStr.AsSpan(i - 2).StartsWith("...".AsSpan()))
// Are the last two chars (except whitespaces) = ".."
let isLikeRangeOp =
match FindFirstNonWhitespacePosition lineStr (colAtEndOfNamesAndResidue - 1) with
- | Some x when x >= 1 && lineStr[x] = '.' && lineStr[x - 1] = '.' -> true
+ | Some x when not isSpread && x >= 1 && lineStr[x] = '.' && lineStr[x - 1] = '.' -> true
| _ -> false
// if last two chars are .. and we are not in range operator context - no completion
@@ -1601,7 +1606,7 @@ type internal TypeCheckInfo
|> Option.orElseWith (fun _ -> FindFirstNonWhitespacePosition lineStr (colAtEndOfNamesAndResidue - 1))
match lastPos with
- | Some p when lineStr[p] = '.' ->
+ | Some p when not isSpread && lineStr[p] = '.' ->
match FindFirstNonWhitespacePosition lineStr (p - 1) with
| Some colAtEndOfNames ->
let colAtEndOfNames = colAtEndOfNames + 1 // convert 0-based to 1-based
@@ -1640,7 +1645,7 @@ type internal TypeCheckInfo
lastDotPos
|> Option.orElseWith (fun _ -> FindFirstNonWhitespacePosition lineStr (colAtEndOfNamesAndResidue - 1))
with
- | Some p when lineStr[p] = '.' ->
+ | Some p when not isSpread && lineStr[p] = '.' ->
match FindFirstNonWhitespacePosition lineStr (p - 1) with
| Some colAtEndOfNames ->
let colAtEndOfNames = colAtEndOfNames + 1 // convert 0-based to 1-based
@@ -1970,6 +1975,44 @@ type internal TypeCheckInfo
// No completion at '...: string'
| Some(CompletionContext.RecordField(RecordContext.Declaration true)) -> None
+ // Completion at 'let r = { ...| }'
+ | Some(CompletionContext.RecordSpread RecordSpreadContext.Construction) ->
+ let envItems = getDeclaredItemsNotInRangeOpWithAllSymbols ()
+
+ envItems
+ |> Option.map (fun (items, denv, m) ->
+ let items =
+ [
+ for completionItem in items do
+ match completionItem.Item with
+ | Item.Value vref when isRecdTy g vref.Type || isAnonRecdTy g vref.Type -> completionItem
+ | _ -> ()
+ ]
+
+ items, denv, m)
+
+ // Completion at 'type R = { ...| }'
+ | Some(CompletionContext.RecordSpread RecordSpreadContext.Declaration) ->
+ let (nenv, ad), m = GetBestEnvForPos pos
+ let recordTycons = getRecordTyconsInScope g ncenv nenv ad m
+
+ let completionItems =
+ [
+ for tcref, item in recordTycons ->
+ {
+ ItemWithInst = ItemWithNoInst item
+ Kind = CompletionItemKind.Other
+ MinorPriority = 0
+ IsOwnMember = false
+ Type = Some tcref
+ Unresolved = None
+ CustomInsertText = ValueNone
+ CustomDisplayText = ValueNone
+ }
+ ]
+
+ Some(completionItems, nenv.DisplayEnv, m)
+
// Completion at ' SomeMethod( ... ) ' or ' [] ' with named arguments
| Some(CompletionContext.ParameterList(endPos, fields)) ->
let results =
@@ -2858,7 +2901,6 @@ type FSharpParsingOptions =
DiagnosticOptions: FSharpDiagnosticOptions
LangVersionText: string
IsInteractive: bool
- StrictIndentation: bool option
CompilingFSharpCore: bool
IsExe: bool
}
@@ -2875,7 +2917,6 @@ type FSharpParsingOptions =
DiagnosticOptions = FSharpDiagnosticOptions.Default
LangVersionText = LanguageVersion.Default.VersionText
IsInteractive = false
- StrictIndentation = None
CompilingFSharpCore = false
IsExe = false
}
@@ -2888,7 +2929,6 @@ type FSharpParsingOptions =
DiagnosticOptions = tcConfig.diagnosticsOptions
LangVersionText = tcConfig.langVersion.VersionText
IsInteractive = isInteractive
- StrictIndentation = tcConfig.strictIndentation
CompilingFSharpCore = tcConfig.compilingFSharpCore
IsExe = tcConfig.target.IsExe
}
@@ -2901,7 +2941,6 @@ type FSharpParsingOptions =
DiagnosticOptions = tcConfigB.diagnosticsOptions
LangVersionText = tcConfigB.langVersion.VersionText
IsInteractive = isInteractive
- StrictIndentation = tcConfigB.strictIndentation
CompilingFSharpCore = tcConfigB.compilingFSharpCore
IsExe = tcConfigB.target.IsExe
}
@@ -3013,8 +3052,8 @@ module internal ParseAndCheckFile =
else
(fun _ -> tokenizer.GetToken())
- let createLexbuf langVersion strictIndentation sourceText =
- UnicodeLexing.SourceTextAsLexbuf(true, LanguageVersion(langVersion), strictIndentation, sourceText)
+ let createLexbuf langVersion sourceText =
+ UnicodeLexing.SourceTextAsLexbuf(true, LanguageVersion(langVersion), sourceText)
let matchBraces
(
@@ -3034,7 +3073,7 @@ module internal ParseAndCheckFile =
let matchingBraces = ResizeArray<_>()
- usingLexbufForParsing (createLexbuf options.LangVersionText options.StrictIndentation sourceText, fileName) (fun lexbuf ->
+ usingLexbufForParsing (createLexbuf options.LangVersionText sourceText, fileName) (fun lexbuf ->
let errHandler =
DiagnosticsHandler(false, fileName, options.DiagnosticOptions, suggestNamesForErrors, false)
@@ -3147,7 +3186,7 @@ module internal ParseAndCheckFile =
use _ = UseBuildPhase BuildPhase.Parse
let parseResult =
- usingLexbufForParsing (createLexbuf options.LangVersionText options.StrictIndentation sourceText, fileName) (fun lexbuf ->
+ usingLexbufForParsing (createLexbuf options.LangVersionText sourceText, fileName) (fun lexbuf ->
let lexfun = createLexerFunction options lexbuf errHandler ct
diff --git a/src/Compiler/Service/FSharpCheckerResults.fsi b/src/Compiler/Service/FSharpCheckerResults.fsi
index 9b5a95c28a9..b1b5f78f675 100644
--- a/src/Compiler/Service/FSharpCheckerResults.fsi
+++ b/src/Compiler/Service/FSharpCheckerResults.fsi
@@ -224,8 +224,6 @@ type public FSharpParsingOptions =
IsInteractive: bool
- StrictIndentation: bool option
-
CompilingFSharpCore: bool
IsExe: bool
diff --git a/src/Compiler/Service/FSharpParseFileResults.fs b/src/Compiler/Service/FSharpParseFileResults.fs
index 119669f22d9..7fd257947b5 100644
--- a/src/Compiler/Service/FSharpParseFileResults.fs
+++ b/src/Compiler/Service/FSharpParseFileResults.fs
@@ -633,14 +633,26 @@ type FSharpParseFileResults(diagnostics: FSharpDiagnostic[], input: ParsedInput,
| Some(e, _) -> yield! walkExpr true e
| None -> ()
- yield! walkExprs (fs |> List.choose (fun (SynExprRecordField(expr = e)) -> e))
+ yield!
+ walkExprs (
+ fs
+ |> List.choose (function
+ | SynExprRecordFieldOrSpread.Field(SynExprRecordField(expr = e), _) -> e
+ | SynExprRecordFieldOrSpread.Spread(spread = SynExprSpread(expr = e)) -> Some e)
+ )
| SynExpr.AnonRecd(copyInfo = copyExprOpt; recordFields = fs) ->
match copyExprOpt with
| Some(e, _) -> yield! walkExpr true e
| None -> ()
- yield! walkExprs (fs |> List.map (fun (_, _, e) -> e))
+ yield!
+ walkExprs (
+ fs
+ |> List.map (function
+ | SynExprAnonRecordFieldOrSpread.Field(SynExprAnonRecordField(_, _, e, _), _)
+ | SynExprAnonRecordFieldOrSpread.Spread(spread = SynExprSpread(expr = e)) -> e)
+ )
| SynExpr.ObjExpr(argOptions = args; bindings = bs; members = ms; extraImpls = is) ->
let bs = unionBindingAndMembers bs ms
diff --git a/src/Compiler/Service/ServiceInterfaceStubGenerator.fs b/src/Compiler/Service/ServiceInterfaceStubGenerator.fs
index 2687b4e0f54..096ea38438f 100644
--- a/src/Compiler/Service/ServiceInterfaceStubGenerator.fs
+++ b/src/Compiler/Service/ServiceInterfaceStubGenerator.fs
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
+// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
namespace FSharp.Compiler.EditorServices
@@ -850,7 +850,11 @@ module InterfaceStubGenerator =
| SynExpr.ArrayOrList(_, synExprList, _range) -> List.tryPick walkExpr synExprList
| SynExpr.Record(_inheritOpt, _copyOpt, fields, _range) ->
- List.tryPick (fun (SynExprRecordField(expr = e)) -> Option.bind walkExpr e) fields
+ List.tryPick
+ (function
+ | SynExprRecordFieldOrSpread.Field(SynExprRecordField(expr = e), _) -> Option.bind walkExpr e
+ | SynExprRecordFieldOrSpread.Spread(spread = SynExprSpread(expr = e)) -> walkExpr e)
+ fields
| SynExpr.New(_, _synType, synExpr, _range) -> walkExpr synExpr
diff --git a/src/Compiler/Service/ServiceLexing.fs b/src/Compiler/Service/ServiceLexing.fs
index 5ce87706c51..ce501ac7755 100644
--- a/src/Compiler/Service/ServiceLexing.fs
+++ b/src/Compiler/Service/ServiceLexing.fs
@@ -63,6 +63,7 @@ module FSharpTokenTag =
let DOT = tagOfToken DOT
let DOT_DOT = tagOfToken DOT_DOT
let DOT_DOT_HAT = tagOfToken DOT_DOT_HAT
+ let DOT_DOT_DOT = tagOfToken DOT_DOT_DOT
let INT32_DOT_DOT = tagOfToken (INT32_DOT_DOT(0, true))
let UNDERSCORE = tagOfToken UNDERSCORE
let BAR = tagOfToken BAR
@@ -233,7 +234,8 @@ module internal TokenClassifications =
| INFIX_AMP_OP _ -> (FSharpTokenColorKind.Operator, FSharpTokenCharKind.Operator, FSharpTokenTriggerClass.None)
| DOT_DOT
- | DOT_DOT_HAT -> (FSharpTokenColorKind.Operator, FSharpTokenCharKind.Operator, FSharpTokenTriggerClass.MemberSelect)
+ | DOT_DOT_HAT
+ | DOT_DOT_DOT -> (FSharpTokenColorKind.Operator, FSharpTokenCharKind.Operator, FSharpTokenTriggerClass.MemberSelect)
| COMMA -> (FSharpTokenColorKind.Punctuation, FSharpTokenCharKind.Delimiter, FSharpTokenTriggerClass.ParamNext)
@@ -1130,8 +1132,7 @@ type FSharpLineTokenizer(lexbuf: UnicodeLexing.Lexbuf, maxLength: int option, fi
}
[]
-type FSharpSourceTokenizer
- (conditionalDefines: string list, fileName: string option, langVersion: string option, strictIndentation: bool option) =
+type FSharpSourceTokenizer(conditionalDefines: string list, fileName: string option, langVersion: string option) =
let langVersion =
langVersion
@@ -1149,13 +1150,13 @@ type FSharpSourceTokenizer
member _.CreateLineTokenizer(lineText: string) =
let lexbuf =
- UnicodeLexing.StringAsLexbuf(reportLibraryOnlyFeatures, langVersion, strictIndentation, lineText)
+ UnicodeLexing.StringAsLexbuf(reportLibraryOnlyFeatures, langVersion, lineText)
FSharpLineTokenizer(lexbuf, Some lineText.Length, fileName, lexargs)
member _.CreateBufferTokenizer bufferFiller =
let lexbuf =
- UnicodeLexing.FunctionAsLexbuf(reportLibraryOnlyFeatures, langVersion, strictIndentation, bufferFiller)
+ UnicodeLexing.FunctionAsLexbuf(reportLibraryOnlyFeatures, langVersion, bufferFiller)
FSharpLineTokenizer(lexbuf, None, fileName, lexargs)
@@ -1322,6 +1323,7 @@ type FSharpTokenKind =
| End
| DotDot
| DotDotHat
+ | DotDotDot
| BarBar
| Upcast
| Downcast
@@ -1521,6 +1523,7 @@ type FSharpToken =
| END -> FSharpTokenKind.End
| DOT_DOT -> FSharpTokenKind.DotDot
| DOT_DOT_HAT -> FSharpTokenKind.DotDotHat
+ | DOT_DOT_DOT -> FSharpTokenKind.DotDotDot
| BAR_BAR -> FSharpTokenKind.BarBar
| UPCAST -> FSharpTokenKind.Upcast
| DOWNCAST -> FSharpTokenKind.Downcast
@@ -1731,7 +1734,6 @@ module FSharpLexerImpl =
(flags: FSharpLexerFlags)
reportLibraryOnlyFeatures
langVersion
- strictIndentation
diagnosticsLogger
onToken
pathMap
@@ -1750,7 +1752,7 @@ module FSharpLexerImpl =
(flags &&& FSharpLexerFlags.UseLexFilter) = FSharpLexerFlags.UseLexFilter
let lexbuf =
- UnicodeLexing.SourceTextAsLexbuf(reportLibraryOnlyFeatures, langVersion, strictIndentation, text)
+ UnicodeLexing.SourceTextAsLexbuf(reportLibraryOnlyFeatures, langVersion, text)
let applyLineDirectives = isCompiling
@@ -1776,7 +1778,7 @@ module FSharpLexerImpl =
ct.ThrowIfCancellationRequested()
onToken (getNextToken lexbuf) lexbuf.LexemeRange
- let lex text conditionalDefines flags reportLibraryOnlyFeatures langVersion strictIndentation lexCallback pathMap ct =
+ let lex text conditionalDefines flags reportLibraryOnlyFeatures langVersion lexCallback pathMap ct =
let diagnosticsLogger =
CompilationDiagnosticLogger("Lexer", FSharpDiagnosticOptions.Default)
@@ -1786,7 +1788,6 @@ module FSharpLexerImpl =
flags
reportLibraryOnlyFeatures
langVersion
- strictIndentation
diagnosticsLogger
lexCallback
pathMap
@@ -1795,9 +1796,7 @@ module FSharpLexerImpl =
[]
type FSharpLexer =
- static member Tokenize
- (text: ISourceText, tokenCallback, ?langVersion, ?strictIndentation, ?filePath: string, ?conditionalDefines, ?flags, ?pathMap, ?ct)
- =
+ static member Tokenize(text: ISourceText, tokenCallback, ?langVersion, ?filePath: string, ?conditionalDefines, ?flags, ?pathMap, ?ct) =
let langVersion = defaultArg langVersion "latestmajor" |> LanguageVersion
let flags = defaultArg flags FSharpLexerFlags.Default
ignore filePath // can be removed at later point
@@ -1817,4 +1816,4 @@ type FSharpLexer =
| _ -> tokenCallback fsTok
let reportLibraryOnlyFeatures = true
- lex text conditionalDefines flags reportLibraryOnlyFeatures langVersion strictIndentation onToken pathMap ct
+ lex text conditionalDefines flags reportLibraryOnlyFeatures langVersion onToken pathMap ct
diff --git a/src/Compiler/Service/ServiceLexing.fsi b/src/Compiler/Service/ServiceLexing.fsi
index fab55c4645e..ea7d05b60fe 100755
--- a/src/Compiler/Service/ServiceLexing.fsi
+++ b/src/Compiler/Service/ServiceLexing.fsi
@@ -176,9 +176,12 @@ module FSharpTokenTag =
/// Indicates the token is a `..`
val DOT_DOT: int
- /// Indicates the token is a `..`
+ /// Indicates the token is a `..^`
val DOT_DOT_HAT: int
+ /// Indicates the token is a `...`
+ val DOT_DOT_DOT: int
+
/// Indicates the token is a `..^`
val INT32_DOT_DOT: int
@@ -324,12 +327,7 @@ type FSharpLineTokenizer =
type FSharpSourceTokenizer =
/// Create a tokenizer for a source file.
- new:
- conditionalDefines: string list *
- fileName: string option *
- langVersion: string option *
- strictIndentation: bool option ->
- FSharpSourceTokenizer
+ new: conditionalDefines: string list * fileName: string option * langVersion: string option -> FSharpSourceTokenizer
/// Create a tokenizer for a line of this source file
member CreateLineTokenizer: lineText: string -> FSharpLineTokenizer
@@ -500,6 +498,7 @@ type public FSharpTokenKind =
| End
| DotDot
| DotDotHat
+ | DotDotDot
| BarBar
| Upcast
| Downcast
@@ -580,7 +579,6 @@ type public FSharpLexer =
text: ISourceText *
tokenCallback: (FSharpToken -> unit) *
?langVersion: string *
- ?strictIndentation: bool *
?filePath: string *
?conditionalDefines: string list *
?flags: FSharpLexerFlags *
diff --git a/src/Compiler/Service/ServiceNavigation.fs b/src/Compiler/Service/ServiceNavigation.fs
index a56b4d4eb6e..2da61ee108e 100755
--- a/src/Compiler/Service/ServiceNavigation.fs
+++ b/src/Compiler/Service/ServiceNavigation.fs
@@ -289,12 +289,14 @@ module NavigationImpl =
createTypeDecl (baseName, lid, FSharpGlyph.Enum, m, mBody, nested, NavigationEntityKind.Enum, access)
]
- | SynTypeDefnSimpleRepr.Record(_, fields, mBody) ->
+ | SynTypeDefnSimpleRepr.Record(_, fieldsAndSpreads, mBody) ->
let fields =
[
- for SynField(idOpt = id; range = m) in fields do
- match id with
- | Some ident -> yield createMember (ident, NavigationItemKind.Field, FSharpGlyph.Field, m, NavigationEntityKind.Record, false, access)
+ for fieldOrSpread in fieldsAndSpreads do
+ match fieldOrSpread with
+ | SynFieldOrSpread.Field(SynField(idOpt = Some ident; range = m)) ->
+ yield createMember (ident, NavigationItemKind.Field, FSharpGlyph.Field, m, NavigationEntityKind.Record, false, access)
+ | SynFieldOrSpread.Spread _
| _ -> ()
]
@@ -546,12 +548,14 @@ module NavigationImpl =
let nested = cases @ topMembers
let mBody = bodyRange mBody nested
createTypeDecl (baseName, lid, FSharpGlyph.Enum, m, mBody, nested, NavigationEntityKind.Enum, access)
- | SynTypeDefnSimpleRepr.Record(_, fields, mBody) ->
+ | SynTypeDefnSimpleRepr.Record(_, fieldsAndSpreads, mBody) ->
let fields =
[
- for SynField(idOpt = id; range = m) in fields do
- match id with
- | Some ident -> yield createMember (ident, NavigationItemKind.Field, FSharpGlyph.Field, m, NavigationEntityKind.Record, false, access)
+ for fieldOrSpread in fieldsAndSpreads do
+ match fieldOrSpread with
+ | SynFieldOrSpread.Field(SynField(idOpt = Some ident; range = m)) ->
+ yield createMember (ident, NavigationItemKind.Field, FSharpGlyph.Field, m, NavigationEntityKind.Record, false, access)
+ | SynFieldOrSpread.Spread _
| _ -> ()
]
@@ -994,10 +998,12 @@ module NavigateTo =
| SynTypeDefnSimpleRepr.Enum(enumCases, _) ->
for c in enumCases do
addEnumCase c isSig container
- | SynTypeDefnSimpleRepr.Record(_, fields, _) ->
- for f in fields do
+ | SynTypeDefnSimpleRepr.Record(_, fieldsAndSpreads, _) ->
+ for fieldOrSpread in fieldsAndSpreads do
// TODO: add specific case for record field?
- addField f isSig container
+ match fieldOrSpread with
+ | SynFieldOrSpread.Field f -> addField f isSig container
+ | SynFieldOrSpread.Spread _ -> ()
| SynTypeDefnSimpleRepr.Union(_, unionCases, _) ->
for uc in unionCases do
addUnionCase uc isSig container
diff --git a/src/Compiler/Service/ServiceParseTreeWalk.fs b/src/Compiler/Service/ServiceParseTreeWalk.fs
index 4a1177b7b26..4b1df951386 100644
--- a/src/Compiler/Service/ServiceParseTreeWalk.fs
+++ b/src/Compiler/Service/ServiceParseTreeWalk.fs
@@ -110,10 +110,10 @@ type SyntaxVisitorBase<'T>() =
None
/// VisitRecordDefn allows overriding behavior when visiting record definitions (by default do nothing)
- abstract VisitRecordDefn: path: SyntaxVisitorPath * fields: SynField list * range -> 'T option
+ abstract VisitRecordDefn: path: SyntaxVisitorPath * fieldsAndSpreads: SynFieldOrSpread list * range -> 'T option
- default _.VisitRecordDefn(path, fields, range) =
- ignore (path, fields, range)
+ default _.VisitRecordDefn(path, fieldsAndSpreads, range) =
+ ignore (path, fieldsAndSpreads, range)
None
/// VisitUnionDefn allows overriding behavior when visiting union definitions (by default do nothing)
@@ -458,9 +458,14 @@ module SyntaxTraversal =
None)
| _ -> ()
- for field, _, x in fields do
- yield dive () field.Range (fun () -> visitor.VisitRecordField(path, copyOpt |> Option.map fst, Some field))
- yield dive x x.Range traverseSynExpr
+ for fieldOrSpread in fields do
+ match fieldOrSpread with
+ | SynExprAnonRecordFieldOrSpread.Field(SynExprAnonRecordField(field, _, x, _), _) ->
+ yield dive () field.Range (fun () -> visitor.VisitRecordField(path, copyOpt |> Option.map fst, Some field))
+ yield dive x x.Range traverseSynExpr
+ | SynExprAnonRecordFieldOrSpread.Spread(spread = SynExprSpread(expr = expr; range = m)) ->
+ yield dive () m (fun () -> visitor.VisitExpr(path, traverseSynExpr, traverseSynExpr, expr))
+ yield dive expr expr.Range traverseSynExpr
]
|> pick expr
@@ -525,57 +530,74 @@ module SyntaxTraversal =
let copyOpt = Option.map fst copyOpt
- for SynExprRecordField(fieldName = (field, _); expr = e; blockSeparator = sepOpt) in fields do
- yield
- dive (path, copyOpt, Some field) field.Range (fun r ->
- // Treat the caret placed right after the field name (before '=' or a value) as "inside" the field,
- // but only if the field does not yet have a value.
- //
- // Examples (the '$' marks the caret):
- // { r with Field1$ }
- // { r with
- // Field1$
- // }
- let isCaretAfterFieldNameWithoutValue = (e.IsNone && posEq pos field.Range.End)
-
- if rangeContainsPos field.Range pos || isCaretAfterFieldNameWithoutValue then
- visitor.VisitRecordField r
- else
- None)
-
- let offsideColumn =
- match inheritOpt with
- | Some(_, _, _, _, inheritRange) -> inheritRange.StartColumn
- | None -> field.Range.StartColumn
-
- match e with
- | Some e ->
+ for fieldOrSpread in fields do
+ match fieldOrSpread with
+ | SynExprRecordFieldOrSpread.Field(SynExprRecordField(fieldName = (field, _); expr = e), sepOpt) ->
yield
- dive e e.Range (fun expr ->
- // special case: caret is below field binding
- // field x = 5
- // $
- if
- not (rangeContainsPos e.Range pos)
- && sepOpt.IsNone
- && pos.Column = offsideColumn
- then
- visitor.VisitRecordField(path, copyOpt, None)
+ dive (path, copyOpt, Some field) field.Range (fun r ->
+ // Treat the caret placed right after the field name (before '=' or a value) as "inside" the field,
+ // but only if the field does not yet have a value.
+ //
+ // Examples (the '$' marks the caret):
+ // { r with Field1$ }
+ // { r with
+ // Field1$
+ // }
+ let isCaretAfterFieldNameWithoutValue = (e.IsNone && posEq pos field.Range.End)
+
+ if rangeContainsPos field.Range pos || isCaretAfterFieldNameWithoutValue then
+ visitor.VisitRecordField r
else
- traverseSynExpr expr)
- | None -> ()
-
- match sepOpt with
- | Some(sep, scPosOpt) ->
- yield
- dive () sep (fun () ->
- // special case: caret is between field bindings
- // field1 = 5
- // $
- // field2 = 5
- diveIntoSeparator offsideColumn scPosOpt copyOpt)
- | _ -> ()
-
+ None)
+
+ let offsideColumn =
+ match inheritOpt with
+ | Some(_, _, _, _, inheritRange) -> inheritRange.StartColumn
+ | None -> field.Range.StartColumn
+
+ match e with
+ | Some e ->
+ yield
+ dive e e.Range (fun expr ->
+ // special case: caret is below field binding
+ // field x = 5
+ // $
+ if
+ not (rangeContainsPos e.Range pos)
+ && sepOpt.IsNone
+ && pos.Column = offsideColumn
+ then
+ visitor.VisitRecordField(path, copyOpt, None)
+ else
+ traverseSynExpr expr)
+ | None -> ()
+
+ match sepOpt with
+ | Some(sep, scPosOpt) ->
+ yield
+ dive () sep (fun () ->
+ // special case: caret is between field bindings
+ // field1 = 5
+ // $
+ // field2 = 5
+ diveIntoSeparator offsideColumn scPosOpt copyOpt)
+ | None -> ()
+
+ | SynExprRecordFieldOrSpread.Spread(SynExprSpread(spreadRange = spreadRange; expr = expr; range = m), sepOpt) ->
+ yield dive () m (fun () -> visitor.VisitExpr(path, traverseSynExpr, traverseSynExpr, expr))
+ yield dive expr expr.Range traverseSynExpr
+
+ match sepOpt with
+ | Some(sep, scPosOpt) ->
+ yield
+ dive () sep (fun () ->
+ // special case: caret is between field bindings
+ // field1 = 5
+ // $
+ // field2 = 5
+ let offsideColumn = spreadRange.StartColumn
+ diveIntoSeparator offsideColumn scPosOpt copyOpt)
+ | None -> ()
]
|> pick expr
@@ -909,10 +931,13 @@ module SyntaxTraversal =
]
|> pick tRange tydef
- and traverseRecordDefn path fields m =
- fields
- |> List.tryPick (fun (SynField(attributes = attributes)) -> attributeApplicationDives path attributes |> pick m attributes)
- |> Option.orElseWith (fun () -> visitor.VisitRecordDefn(path, fields, m))
+ and traverseRecordDefn path fieldsAndSpreads m =
+ fieldsAndSpreads
+ |> List.tryPick (function
+ | SynFieldOrSpread.Field(SynField(attributes = attributes)) ->
+ attributeApplicationDives path attributes |> pick m attributes
+ | SynFieldOrSpread.Spread _ -> None)
+ |> Option.orElseWith (fun () -> visitor.VisitRecordDefn(path, fieldsAndSpreads, m))
and traverseEnumDefn path cases m =
cases
@@ -1160,7 +1185,12 @@ module SyntaxTraversal =
module SyntaxNode =
let (|Attributes|) node =
let (|All|) = List.collect
- let field (SynField(attributes = attributes)) = attributes
+
+ let fieldOrSpread =
+ function
+ | SynFieldOrSpread.Field(SynField(attributes = attributes)) -> attributes
+ | SynFieldOrSpread.Spread _ -> []
+
let unionCase (SynUnionCase(attributes = attributes)) = attributes
let enumCase (SynEnumCase(attributes = attributes)) = attributes
let typar (SynTyparDecl(attributes = attributes)) = attributes
@@ -1186,7 +1216,7 @@ module SyntaxNode =
| SyntaxNode.SynModule(SynModuleDecl.Attributes(attributes = attributes))
| SyntaxNode.SynTypeDefn(SynTypeDefn(typeInfo = SynComponentInfo attributes))
| SyntaxNode.SynTypeDefn(SynTypeDefn(
- typeRepr = SynTypeDefnRepr.Simple(SynTypeDefnSimpleRepr.Record(recordFields = All field attributes), _)))
+ typeRepr = SynTypeDefnRepr.Simple(SynTypeDefnSimpleRepr.Record(recordFieldsAndSpreads = All fieldOrSpread attributes), _)))
| SyntaxNode.SynTypeDefn(SynTypeDefn(
typeRepr = SynTypeDefnRepr.Simple(SynTypeDefnSimpleRepr.Union(unionCases = All unionCase attributes), _)))
| SyntaxNode.SynTypeDefn(SynTypeDefn(
diff --git a/src/Compiler/Service/ServiceParseTreeWalk.fsi b/src/Compiler/Service/ServiceParseTreeWalk.fsi
index ab9e98f6e81..d8a9e142148 100644
--- a/src/Compiler/Service/ServiceParseTreeWalk.fsi
+++ b/src/Compiler/Service/ServiceParseTreeWalk.fsi
@@ -101,8 +101,8 @@ type SyntaxVisitorBase<'T> =
range: range ->
'T option
- abstract VisitRecordDefn: path: SyntaxVisitorPath * fields: SynField list * range -> 'T option
- default VisitRecordDefn: path: SyntaxVisitorPath * fields: SynField list * range -> 'T option
+ abstract VisitRecordDefn: path: SyntaxVisitorPath * fieldsAndSpreads: SynFieldOrSpread list * range -> 'T option
+ default VisitRecordDefn: path: SyntaxVisitorPath * fieldsAndSpreads: SynFieldOrSpread list * range -> 'T option
abstract VisitUnionDefn: path: SyntaxVisitorPath * cases: SynUnionCase list * range -> 'T option
default VisitUnionDefn: path: SyntaxVisitorPath * cases: SynUnionCase list * range -> 'T option
diff --git a/src/Compiler/Service/ServiceParsedInputOps.fs b/src/Compiler/Service/ServiceParsedInputOps.fs
index 00dbde0eae9..cfc181ef355 100644
--- a/src/Compiler/Service/ServiceParsedInputOps.fs
+++ b/src/Compiler/Service/ServiceParsedInputOps.fs
@@ -50,6 +50,14 @@ type RecordContext =
| New of path: CompletionPath * isFirstField: bool
| Declaration of isInIdentifier: bool
+[]
+type RecordSpreadContext =
+ /// type R = { ...| }
+ | Declaration
+
+ /// let r = { ...| }
+ | Construction
+
[]
type PatternContext =
/// Completing union case field pattern (e.g. fun (Some v| ) -> ) or fun (Some (v| )) -> ). In theory, this could also be parameterized active pattern usage.
@@ -87,6 +95,9 @@ type CompletionContext =
/// Completing records field
| RecordField of context: RecordContext
+ /// Completing a record spread: { ...| }
+ | RecordSpread of context: RecordSpreadContext
+
| RangeOperator
/// Completing named parameters\setters in parameter list of attributes\constructor\method calls
@@ -808,7 +819,10 @@ module ParsedInput =
| SynExpr.Record(_, _, fields, r) ->
ifPosInRange r (fun _ ->
fields
- |> List.tryPick (fun (SynExprRecordField(expr = e)) -> e |> Option.bind (walkExprWithKind parentKind)))
+ |> List.tryPick (function
+ | SynExprRecordFieldOrSpread.Field(SynExprRecordField(expr = e), _) ->
+ e |> Option.bind (walkExprWithKind parentKind)
+ | SynExprRecordFieldOrSpread.Spread(spread = SynExprSpread(expr = e)) -> walkExprWithKind parentKind e))
| SynExpr.ObjExpr(objType = ty; bindings = bindings; members = ms; extraImpls = ifaces) ->
let bindings = unionBindingAndMembers bindings ms
@@ -856,6 +870,8 @@ module ParsedInput =
let (SynField(attributes = Attributes attrs; fieldType = t)) = synField
List.tryPick walkAttribute attrs |> Option.orElseWith (fun () -> walkType t)
+ and walkTypeSpread (SynTypeSpread(ty = ty)) = walkType ty
+
and walkValSig synValSig =
let (SynValSig(attributes = Attributes attrs; synType = t)) = synValSig
List.tryPick walkAttribute attrs |> Option.orElseWith (fun () -> walkType t)
@@ -929,7 +945,12 @@ module ParsedInput =
match synTypeDefn with
| SynTypeDefnSimpleRepr.Enum(cases, _) -> List.tryPick walkEnumCase cases
| SynTypeDefnSimpleRepr.Union(_, cases, _) -> List.tryPick walkUnionCase cases
- | SynTypeDefnSimpleRepr.Record(_, fields, _) -> List.tryPick walkField fields
+ | SynTypeDefnSimpleRepr.Record(_, fields, _) ->
+ List.tryPick
+ (function
+ | SynFieldOrSpread.Field field -> walkField field
+ | SynFieldOrSpread.Spread spread -> walkTypeSpread spread)
+ fields
| SynTypeDefnSimpleRepr.TypeAbbrev(_, t, _) -> walkType t
| _ -> None
@@ -1479,6 +1500,26 @@ module ParsedInput =
->
Some(CompletionContext.Inherit(InheritanceContext.Unknown, ([], None)))
+ // { ...$ }
+ | SynExpr.Record(recordFields = fields) ->
+ fields
+ |> List.tryPick (function
+ | SynExprRecordFieldOrSpread.Spread(SynExprSpread(expr = expr), _) when rangeContainsPos expr.Range pos ->
+ Some(CompletionContext.RecordSpread RecordSpreadContext.Construction)
+ | SynExprRecordFieldOrSpread.Spread _
+ | SynExprRecordFieldOrSpread.Field _ -> None)
+ |> Option.orElseWith (fun () -> defaultTraverse expr)
+
+ // {| ...$ |}
+ | SynExpr.AnonRecd(recordFields = fields) ->
+ fields
+ |> List.tryPick (function
+ | SynExprAnonRecordFieldOrSpread.Spread(SynExprSpread(expr = expr), _) when rangeContainsPos expr.Range pos ->
+ Some(CompletionContext.RecordSpread RecordSpreadContext.Construction)
+ | SynExprAnonRecordFieldOrSpread.Spread _
+ | SynExprAnonRecordFieldOrSpread.Field _ -> None)
+ |> Option.orElseWith (fun () -> defaultTraverse expr)
+
| _ -> defaultTraverse expr
member _.VisitRecordField(path, copyOpt, field) =
@@ -1488,10 +1529,12 @@ module ParsedInput =
| SyntaxNode.SynExpr _ :: SyntaxNode.SynBinding _ :: SyntaxNode.SynMemberDefn _ :: SyntaxNode.SynTypeDefn(SynTypeDefn(
typeInfo = SynComponentInfo(longId = [ id ]))) :: _ -> RecordContext.Constructor(id.idText)
- | SyntaxNode.SynExpr(SynExpr.Record(None, _, fields, _)) :: _ ->
+ | SyntaxNode.SynExpr(SynExpr.Record(None, _, fieldsAndSpreads, _)) :: _ ->
let isFirstField =
- match field, fields with
- | Some contextLid, SynExprRecordField(fieldName = lid, _) :: _ -> contextLid.Range = lid.Range
+ match field, fieldsAndSpreads with
+ | Some contextLid, SynExprRecordFieldOrSpread.Field(SynExprRecordField(fieldName = lid, _), _) :: _ ->
+ contextLid.Range = lid.Range
+ | Some _, SynExprRecordFieldOrSpread.Spread _ :: _ -> false
| _ -> false
RecordContext.New(completionPath, isFirstField)
@@ -1780,13 +1823,19 @@ module ParsedInput =
member _.VisitRecordDefn(_, fields, range) =
fields
- |> List.tryPick (fun (SynField(idOpt = idOpt; range = fieldRange; fieldType = fieldType)) ->
- match idOpt, fieldType with
- | Some id, _ when rangeContainsPos id.idRange pos ->
- Some(CompletionContext.RecordField(RecordContext.Declaration true))
- | _ when rangeContainsPos fieldRange pos -> Some(CompletionContext.RecordField(RecordContext.Declaration false))
- | _, SynType.FromParseError _ -> Some(CompletionContext.RecordField(RecordContext.Declaration false))
- | _ -> None)
+ |> List.tryPick (function
+ | SynFieldOrSpread.Field(SynField(idOpt = idOpt; range = fieldRange; fieldType = fieldType)) ->
+ match idOpt, fieldType with
+ | Some id, _ when rangeContainsPos id.idRange pos ->
+ Some(CompletionContext.RecordField(RecordContext.Declaration true))
+ | _ when rangeContainsPos fieldRange pos -> Some(CompletionContext.RecordField(RecordContext.Declaration false))
+ | _, SynType.FromParseError _ -> Some(CompletionContext.RecordField(RecordContext.Declaration false))
+ | _ -> None
+ | SynFieldOrSpread.Spread(SynTypeSpread(ty = ty)) ->
+ if rangeContainsPos ty.Range pos then
+ Some(CompletionContext.RecordSpread RecordSpreadContext.Declaration)
+ else
+ None)
// No completions in a record outside of all fields, except in attributes, which is established earlier in VisitAttributeApplication
|> Option.orElseWith (fun _ ->
if rangeContainsPos range pos then
@@ -2072,9 +2121,11 @@ module ParsedInput =
| SynExpr.Record(recordFields = fields) ->
fields
- |> List.iter (fun (SynExprRecordField(fieldName = (ident, _); expr = e)) ->
- addLongIdentWithDots ident
- e |> Option.iter walkExpr)
+ |> List.iter (function
+ | SynExprRecordFieldOrSpread.Field(SynExprRecordField(fieldName = (ident, _); expr = e), _) ->
+ addLongIdentWithDots ident
+ e |> Option.iter walkExpr
+ | SynExprRecordFieldOrSpread.Spread(spread = SynExprSpread(expr = e)) -> walkExpr e)
| SynExpr.Ident ident -> addIdent ident
@@ -2197,6 +2248,8 @@ module ParsedInput =
List.iter walkAttribute attrs
walkType t
+ and walkTypeSpread (SynTypeSpread(ty = ty)) = walkType ty
+
and walkValSig (SynValSig(attributes = Attributes attrs; synType = t; arity = SynValInfo(argInfos, argInfo))) =
List.iter walkAttribute attrs
walkType t
@@ -2268,7 +2321,12 @@ module ParsedInput =
match typeDefn with
| SynTypeDefnSimpleRepr.Enum(cases, _) -> List.iter walkEnumCase cases
| SynTypeDefnSimpleRepr.Union(_, cases, _) -> List.iter walkUnionCase cases
- | SynTypeDefnSimpleRepr.Record(_, fields, _) -> List.iter walkField fields
+ | SynTypeDefnSimpleRepr.Record(_, fields, _) ->
+ List.iter
+ (function
+ | SynFieldOrSpread.Field field -> walkField field
+ | SynFieldOrSpread.Spread spread -> walkTypeSpread spread)
+ fields
| SynTypeDefnSimpleRepr.TypeAbbrev(_, t, _) -> walkType t
| _ -> ()
diff --git a/src/Compiler/Service/ServiceParsedInputOps.fsi b/src/Compiler/Service/ServiceParsedInputOps.fsi
index b063468dc50..1b28bfb18d3 100644
--- a/src/Compiler/Service/ServiceParsedInputOps.fsi
+++ b/src/Compiler/Service/ServiceParsedInputOps.fsi
@@ -22,6 +22,14 @@ type public RecordContext =
| New of path: CompletionPath * isFirstField: bool
| Declaration of isInIdentifier: bool
+[]
+type public RecordSpreadContext =
+ /// type R = { ...| }
+ | Declaration
+
+ /// let r = { ...| }
+ | Construction
+
[]
type public PatternContext =
/// Completing union case field pattern (e.g. fun (Some v| ) -> ) or fun (Some (v| )) -> ). In theory, this could also be parameterized active pattern usage.
@@ -59,6 +67,9 @@ type public CompletionContext =
/// Completing records field
| RecordField of context: RecordContext
+ /// Completing a record spread: { ...| }
+ | RecordSpread of context: RecordSpreadContext
+
| RangeOperator
/// Completing named parameters\setters in parameter list of attributes\constructor\method calls
diff --git a/src/Compiler/Service/ServiceStructure.fs b/src/Compiler/Service/ServiceStructure.fs
index 577902a9146..fe85763c675 100644
--- a/src/Compiler/Service/ServiceStructure.fs
+++ b/src/Compiler/Service/ServiceStructure.fs
@@ -440,7 +440,9 @@ module Structure =
| _ -> ()
recordFields
- |> List.choose (fun (SynExprRecordField(expr = e)) -> e)
+ |> List.choose (function
+ | SynExprRecordFieldOrSpread.Field(SynExprRecordField(expr = e), _) -> e
+ | SynExprRecordFieldOrSpread.Spread(spread = SynExprSpread(expr = e)) -> Some e)
|> List.iter parseExpr
// exclude the opening `{` and closing `}` of the record from collapsing
let m = Range.modBoth 1 1 r
@@ -607,12 +609,15 @@ module Structure =
rcheck Scope.EnumCase Collapse.Below cr cr
parseAttributes attrs
- | SynTypeDefnSimpleRepr.Record(_, fields, rr) ->
+ | SynTypeDefnSimpleRepr.Record(_, fieldsAndSpreads, rr) ->
rcheck Scope.RecordDefn Collapse.Same rr rr
- for SynField(attributes = attrs; range = fr) in fields do
- rcheck Scope.RecordField Collapse.Below fr fr
- parseAttributes attrs
+ for fieldOrSpread in fieldsAndSpreads do
+ match fieldOrSpread with
+ | SynFieldOrSpread.Field(SynField(attributes = attrs; range = fr)) ->
+ rcheck Scope.RecordField Collapse.Below fr fr
+ parseAttributes attrs
+ | SynFieldOrSpread.Spread _ -> ()
| SynTypeDefnSimpleRepr.Union(_, cases, ur) ->
rcheck Scope.UnionDefn Collapse.Same ur ur
diff --git a/src/Compiler/Service/SynExpr.fs b/src/Compiler/Service/SynExpr.fs
index 8a81d77193e..ef320e68a97 100644
--- a/src/Compiler/Service/SynExpr.fs
+++ b/src/Compiler/Service/SynExpr.fs
@@ -1087,10 +1087,13 @@ module SynExpr =
| SynExpr.InterpolatedString _, SynExpr.Sequential _
| SynExpr.InterpolatedString _, SynExpr.Tuple(isStruct = false) -> true
+ // Removing the parens would let a trailing alignment or format be parsed as part of the hole,
+ // e.g. the ',-3' in '$"{(if b then 1 else 0),-3}"' becoming a tuple in the else branch.
| SynExpr.InterpolatedString(contents = contents), Dangling.Problematic _ ->
contents
|> List.exists (function
- | SynInterpolatedStringPart.FillExpr(qualifiers = Some _) -> true
+ | SynInterpolatedStringPart.FillExpr(formatting = SynInterpolationFormatting.DotNet(alignment = Some _))
+ | SynInterpolatedStringPart.FillExpr(formatting = SynInterpolationFormatting.DotNet(format = Some _)) -> true
| _ -> false)
// {| A = (1; 2) |}
@@ -1116,8 +1119,13 @@ module SynExpr =
let rec loop recordFields =
match recordFields with
| [] -> false
- | SynExprRecordField(expr = Some(SynExpr.Paren(expr = Is inner)); blockSeparator = Some _) :: SynExprRecordField(
- fieldName = SynLongIdent(id = id :: _), _) :: _ -> problematic inner.Range id.idRange
+ | SynExprRecordFieldOrSpread.Field(
+ field = SynExprRecordField(expr = Some(SynExpr.Paren(expr = Is inner))); blockSeparator = Some _) :: SynExprRecordFieldOrSpread.Field(SynExprRecordField(
+ fieldName = SynLongIdent(
+ id = id :: _),
+ _),
+ _) :: _ ->
+ problematic inner.Range id.idRange
| _ :: recordFields -> loop recordFields
loop recordFields
@@ -1126,8 +1134,8 @@ module SynExpr =
let rec loop recordFields =
match recordFields with
| [] -> false
- | (_, Some _blockSeparator, SynExpr.Paren(expr = Is inner)) :: (SynLongIdent(id = id :: _), _, _) :: _ ->
- problematic inner.Range id.idRange
+ | SynExprAnonRecordFieldOrSpread.Field(SynExprAnonRecordField(_, Some _equalsRange, SynExpr.Paren(expr = Is inner), _),
+ _) :: next :: _ -> problematic inner.Range next.Range
| _ :: recordFields -> loop recordFields
loop recordFields
diff --git a/src/Compiler/Service/TransparentCompiler.fs b/src/Compiler/Service/TransparentCompiler.fs
index fe3caffc6d7..4666aa930ed 100644
--- a/src/Compiler/Service/TransparentCompiler.fs
+++ b/src/Compiler/Service/TransparentCompiler.fs
@@ -2170,7 +2170,6 @@ type internal TransparentCompiler
yield options.ApplyLineDirectives
yield options.DiagnosticOptions.GlobalWarnAsError
yield options.IsInteractive
- yield! (Option.toList options.StrictIndentation)
yield options.CompilingFSharpCore
yield options.IsExe
]
diff --git a/src/Compiler/Service/service.fs b/src/Compiler/Service/service.fs
index c0dd6e21d09..1006def6da1 100644
--- a/src/Compiler/Service/service.fs
+++ b/src/Compiler/Service/service.fs
@@ -623,14 +623,11 @@ type FSharpChecker
static member Instance = globalInstance.Force()
- static member internal CreateOverloadCacheMetricsListener() =
- new CacheMetrics.CacheMetricsListener("overloadResolutionCache")
-
member internal _.FrameworkImportsCache = backgroundCompiler.FrameworkImportsCache
/// Tokenize a single line, returning token information and a tokenization state represented by an integer
member _.TokenizeLine(line: string, state: FSharpTokenizerLexState) =
- let tokenizer = FSharpSourceTokenizer([], None, None, None)
+ let tokenizer = FSharpSourceTokenizer([], None, None)
let lineTokenizer = tokenizer.CreateLineTokenizer line
let mutable state = (None, state)
diff --git a/src/Compiler/Service/service.fsi b/src/Compiler/Service/service.fsi
index ae2b253c676..1584e19562b 100644
--- a/src/Compiler/Service/service.fsi
+++ b/src/Compiler/Service/service.fsi
@@ -506,9 +506,6 @@ type public FSharpChecker =
[]
static member Instance: FSharpChecker
- /// Creates a listener for overload resolution cache metrics, aggregating across all compilations.
- static member internal CreateOverloadCacheMetricsListener: unit -> CacheMetrics.CacheMetricsListener
-
member internal FrameworkImportsCache: FrameworkImportsCache
member internal ReferenceResolver: LegacyReferenceResolver
diff --git a/src/Compiler/SyntaxTree/LexFilter.fs b/src/Compiler/SyntaxTree/LexFilter.fs
index e0e450c398f..8f9267909d7 100644
--- a/src/Compiler/SyntaxTree/LexFilter.fs
+++ b/src/Compiler/SyntaxTree/LexFilter.fs
@@ -771,9 +771,6 @@ type LexFilterImpl (
let relaxWhitespace2 = lexbuf.SupportsFeature LanguageFeature.RelaxWhitespace2
- let strictIndentation =
- lexbuf.StrictIndentation |> Option.defaultWith (fun _ -> lexbuf.SupportsFeature LanguageFeature.StrictIndentation)
-
//let indexerNotationWithoutDot = lexbuf.SupportsFeature LanguageFeature.IndexerNotationWithoutDot
let tryPushCtxt strict ignoreIndent tokenTup (newCtxt: Context) =
@@ -1010,8 +1007,7 @@ type LexFilterImpl (
let isCorrectIndent = c2 >= p1.Column
if not isCorrectIndent then
- let warnF = if strictIndentation then error else warn
- warnF tokenTup
+ error tokenTup
(if debug then
sprintf "possible incorrect indentation: this token is offside of context at (original!) position %s, newCtxt = %A, stack = %A, newCtxtPos = %s, c1 = %d, c2 = %d"
(warningStringOfPosition p1.Position) newCtxt offsideStack (stringOfPos newCtxt.StartPos) p1.Column c2
@@ -2358,7 +2354,7 @@ type LexFilterImpl (
let leadingBar = match peekNextToken() with BAR -> true | _ -> false
if debug then dprintf "WITH, pushing CtxtMatchClauses, lookaheadTokenStartPos = %a, tokenStartPos = %a\n" outputPos lookaheadTokenStartPos outputPos tokenStartPos
- tryPushCtxt strictIndentation false lookaheadTokenTup (CtxtMatchClauses(leadingBar, lookaheadTokenStartPos)) |> ignore
+ tryPushCtxt true false lookaheadTokenTup (CtxtMatchClauses(leadingBar, lookaheadTokenStartPos)) |> ignore
returnToken tokenLexbufState OWITH
@@ -2374,6 +2370,7 @@ type LexFilterImpl (
match lookaheadTokenTup.Token with
| RBRACE _
| IDENT _
+ | DOT_DOT_DOT
// The next clause detects the access annotations after the 'with' in:
// member x.PublicGetSetProperty
// with public get i = "Ralf"
@@ -2414,18 +2411,26 @@ type LexFilterImpl (
//
// with x = ...
//
+ // or
+ //
+ // with ...spreadSrc
+ //
// Which can only be part of
//
// { r with x = ... }
//
+ // or
+ //
+ // { r with ...spreadSrc }
+ //
// and in this case push a CtxtSeqBlock to cover the sequence
- let isFollowedByLongIdentEquals =
+ let isFollowedByLongIdentEqualsOrDotDotDot =
let tokenTup = popNextTokenTup()
- let res = isLongIdentEquals tokenTup.Token
+ let res = isLongIdentEquals tokenTup.Token || match tokenTup.Token with DOT_DOT_DOT -> true | _ -> false
delayToken tokenTup
res
- if isFollowedByLongIdentEquals then
+ if isFollowedByLongIdentEqualsOrDotDotDot then
pushCtxtSeqBlock tokenTup NoAddBlockEnd
returnToken tokenLexbufState OWITH
@@ -2770,10 +2775,10 @@ type LexFilterImpl (
false
and pushCtxtSeqBlock fallbackToken addBlockEnd =
- pushCtxtSeqBlockAt strictIndentation true fallbackToken (peekNextTokenTup ()) addBlockEnd
+ pushCtxtSeqBlockAt true true fallbackToken (peekNextTokenTup ()) addBlockEnd
and tryPushCtxtSeqBlock fallbackToken addBlockEnd =
- pushCtxtSeqBlockAt strictIndentation false fallbackToken (peekNextTokenTup ()) addBlockEnd
+ pushCtxtSeqBlockAt true false fallbackToken (peekNextTokenTup ()) addBlockEnd
and pushCtxtSeqBlockAt strict (useFallback: bool) (fallbackToken: TokenTup) (tokenTup: TokenTup) addBlockEnd =
let pushed = tryPushCtxt strict false tokenTup (CtxtSeqBlock(FirstInSeqBlock, startPosOfTokenTup tokenTup, addBlockEnd))
diff --git a/src/Compiler/SyntaxTree/ParseHelpers.fs b/src/Compiler/SyntaxTree/ParseHelpers.fs
index b329d48ee34..22eb96151e9 100644
--- a/src/Compiler/SyntaxTree/ParseHelpers.fs
+++ b/src/Compiler/SyntaxTree/ParseHelpers.fs
@@ -69,6 +69,52 @@ let rhs2 (parseState: IParseState) i j =
/// Get the range corresponding to one of the r.h.s. symbols of a grammar rule while it is being reduced
let rhs parseState i = rhs2 parseState i i
+/// Split a trailing printf specifier (e.g. "%d") off an interpolated-string literal that precedes a
+/// hole. '%%' is a literal escape, not a specifier.
+let peelTrailingPrintfSpecifier (litText: string) : string * string option =
+ let n = litText.Length
+ let mutable i = 0
+ let mutable specStart = -1
+
+ while i < n && specStart < 0 do
+ if litText[i] = '%' then
+ if i + 1 < n && litText[i + 1] = '%' then
+ i <- i + 2 // '%%' escape, keep scanning
+ else
+ specStart <- i // start of a real specifier
+ else
+ i <- i + 1
+
+ // A real printf specifier ends, immediately before the hole, with a type character. Anything else
+ // (for example the explicit '%P(' placeholder syntax) is left in the literal untouched.
+ if specStart < 0 || "bscdiuxXoBeEfFgGMOAat".IndexOf litText[n - 1] < 0 then
+ litText, None
+ else
+ litText[.. specStart - 1], Some litText[specStart..]
+
+/// Build the [String literal; FillExpr hole] pair for one interpolation hole, splitting the '{x,n}'
+/// alignment out of its tuple encoding and peeling a trailing printf specifier onto the hole.
+let mkInterpolatedStringFillParts (litText: string, litRange: range, fill: SynExpr * Ident option) =
+ let fillExpr, qualifier = fill
+
+ let holeExpr, alignment =
+ match fillExpr with
+ | SynExpr.Tuple(false, [ e; (SynExpr.Const(SynConst.Int32 _, _) as n) ], _, _) -> e, Some n
+ | _ -> fillExpr, None
+
+ let litValue, formatting =
+ match qualifier, alignment with
+ | None, None ->
+ match peelTrailingPrintfSpecifier litText with
+ | lit, Some spec -> lit, SynInterpolationFormatting.Printf(spec, litRange)
+ | _, None -> litText, SynInterpolationFormatting.DotNet(None, None)
+ | _ -> litText, SynInterpolationFormatting.DotNet(alignment, qualifier)
+
+ [
+ SynInterpolatedStringPart.String(litValue, litRange)
+ SynInterpolatedStringPart.FillExpr(holeExpr, formatting)
+ ]
+
//------------------------------------------------------------------------
// Parsing/lexing: status of #if/#endif processing in lexing, used for continuations
// for whitespace tokens in parser specification.
@@ -197,7 +243,7 @@ and LexCont = LexerContinuation
// Parse IL assembly code
//------------------------------------------------------------------------
-let ParseAssemblyCodeInstructions s reportLibraryOnlyFeatures langVersion strictIndentation m : IL.ILInstr[] =
+let ParseAssemblyCodeInstructions s reportLibraryOnlyFeatures langVersion m : IL.ILInstr[] =
#if NO_INLINE_IL_PARSER
ignore s
ignore isFeatureSupported
@@ -206,13 +252,13 @@ let ParseAssemblyCodeInstructions s reportLibraryOnlyFeatures langVersion strict
[||]
#else
try
- AsciiParser.ilInstrs AsciiLexer.token (StringAsLexbuf(reportLibraryOnlyFeatures, langVersion, strictIndentation, s))
+ AsciiParser.ilInstrs AsciiLexer.token (StringAsLexbuf(reportLibraryOnlyFeatures, langVersion, s))
with _ ->
errorR (Error(FSComp.SR.astParseEmbeddedILError (), m))
[||]
#endif
-let ParseAssemblyCodeType s reportLibraryOnlyFeatures langVersion strictIndentation m =
+let ParseAssemblyCodeType s reportLibraryOnlyFeatures langVersion m =
ignore s
#if NO_INLINE_IL_PARSER
@@ -220,7 +266,7 @@ let ParseAssemblyCodeType s reportLibraryOnlyFeatures langVersion strictIndentat
IL.PrimaryAssemblyILGlobals.typ_Object
#else
try
- AsciiParser.ilType AsciiLexer.token (StringAsLexbuf(reportLibraryOnlyFeatures, langVersion, strictIndentation, s))
+ AsciiParser.ilType AsciiLexer.token (StringAsLexbuf(reportLibraryOnlyFeatures, langVersion, s))
with RecoverableParseError ->
errorR (Error(FSComp.SR.astParseEmbeddedILTypeError (), m))
IL.PrimaryAssemblyILGlobals.typ_Object
@@ -720,13 +766,27 @@ let rebindRanges first fields lastSep =
| Some mEq -> unionRanges lidwd.Range mEq
| None -> lidwd.Range
- let rec run (name, mEquals, value: SynExpr option) l acc =
- let lidwd, _ = name
- let fieldRange = calculateFieldRange lidwd mEquals value
+ let rec run fieldOrSpread l acc =
+ match fieldOrSpread with
+ | RecordBinding.Field((lidwd, _ as name), mEquals, value) ->
+ let fieldRange = calculateFieldRange lidwd mEquals value
+
+ match l with
+ | [] ->
+ let field =
+ SynExprRecordFieldOrSpread.Field(SynExprRecordField(name, mEquals, value, fieldRange), lastSep)
+
+ List.rev (field :: acc)
+ | (f, m) :: xs ->
+ let field =
+ SynExprRecordFieldOrSpread.Field(SynExprRecordField(name, mEquals, value, fieldRange), m)
+
+ run f xs (field :: acc)
- match l with
- | [] -> List.rev (SynExprRecordField(name, mEquals, value, fieldRange, lastSep) :: acc)
- | (f, m) :: xs -> run f xs (SynExprRecordField(name, mEquals, value, fieldRange, m) :: acc)
+ | RecordBinding.Spread spread ->
+ match l with
+ | [] -> List.rev (SynExprRecordFieldOrSpread.Spread(spread, lastSep) :: acc)
+ | (f, _) :: xs -> run f xs (SynExprRecordFieldOrSpread.Spread(spread, lastSep) :: acc)
run first fields []
diff --git a/src/Compiler/SyntaxTree/ParseHelpers.fsi b/src/Compiler/SyntaxTree/ParseHelpers.fsi
index aae952d210c..ca58bdb1534 100644
--- a/src/Compiler/SyntaxTree/ParseHelpers.fsi
+++ b/src/Compiler/SyntaxTree/ParseHelpers.fsi
@@ -38,6 +38,16 @@ val rhs2: parseState: IParseState -> i: int -> j: int -> range
val rhs: parseState: IParseState -> i: int -> range
+/// Peel a trailing printf specifier (e.g. "%d") off an interpolated-string literal that precedes a
+/// hole, returning the literal without it and the specifier text. '%%' is a literal escape.
+val peelTrailingPrintfSpecifier: litText: string -> string * string option
+
+/// Build the [String literal; FillExpr hole] pair for one interpolation hole, splitting the
+/// '{x,n}' alignment out of its tuple encoding and peeling a trailing printf specifier off the
+/// literal onto the hole.
+val mkInterpolatedStringFillParts:
+ litText: string * litRange: range * fill: (SynExpr * Ident option) -> SynInterpolatedStringPart list
+
type LexerIfdefStackEntry =
| IfDefIf
| IfDefElse
@@ -105,24 +115,14 @@ type LexerContinuation =
and LexCont = LexerContinuation
val ParseAssemblyCodeInstructions:
- s: string ->
- reportLibraryOnlyFeatures: bool ->
- langVersion: LanguageVersion ->
- strictIndentation: bool option ->
- m: range ->
- ILInstr[]
+ s: string -> reportLibraryOnlyFeatures: bool -> langVersion: LanguageVersion -> m: range -> ILInstr[]
val grabXmlDocAtRangeStart: parseState: IParseState * optAttributes: SynAttributeList list * range: range -> PreXmlDoc
val grabXmlDoc: parseState: IParseState * optAttributes: SynAttributeList list * elemIdx: int -> PreXmlDoc
val ParseAssemblyCodeType:
- s: string ->
- reportLibraryOnlyFeatures: bool ->
- langVersion: LanguageVersion ->
- strictIndentation: bool option ->
- m: range ->
- ILType
+ s: string -> reportLibraryOnlyFeatures: bool -> langVersion: LanguageVersion -> m: range -> ILType
val reportParseErrorAt: range -> (int * string) -> unit
@@ -166,10 +166,10 @@ val exprFromParseError: e: SynExpr -> SynExpr
val patFromParseError: e: SynPat -> SynPat
val rebindRanges:
- first: (RecordFieldName * range option * SynExpr option) ->
- fields: ((RecordFieldName * range option * SynExpr option) * BlockSeparator option) list ->
+ first: RecordBinding ->
+ fields: (RecordBinding * BlockSeparator option) list ->
lastSep: BlockSeparator option ->
- SynExprRecordField list
+ SynExprRecordFieldOrSpread list
val mkUnderscoreRecdField: m: range -> SynLongIdent * bool
diff --git a/src/Compiler/SyntaxTree/SyntaxTree.fs b/src/Compiler/SyntaxTree/SyntaxTree.fs
index f35bb3297de..f5cde6c2b27 100644
--- a/src/Compiler/SyntaxTree/SyntaxTree.fs
+++ b/src/Compiler/SyntaxTree/SyntaxTree.fs
@@ -317,6 +317,11 @@ type BlockSeparator = range * pos option
type RecordFieldName = SynLongIdent * bool
+[]
+type RecordBinding =
+ | Field of name: RecordFieldName * equalsRange: range option * declExpr: SynExpr option
+ | Spread of spread: SynExprSpread
+
type ExprAtomicFlag =
| Atomic = 0
| NonAtomic = 1
@@ -541,7 +546,7 @@ type SynExpr =
| AnonRecd of
isStruct: bool *
copyInfo: (SynExpr * BlockSeparator) option *
- recordFields: (SynLongIdent * range option * SynExpr) list *
+ recordFields: SynExprAnonRecordFieldOrSpread list *
range: range *
trivia: SynExprAnonRecdTrivia
@@ -550,7 +555,7 @@ type SynExpr =
| Record of
baseInfo: (SynType * SynExpr * range * BlockSeparator option * range) option *
copyInfo: (SynExpr * BlockSeparator) option *
- recordFields: SynExprRecordField list *
+ recordFields: SynExprRecordFieldOrSpread list *
range: range
| New of isProtected: bool * targetType: SynType * expr: SynExpr * range: range
@@ -864,18 +869,41 @@ type SynExpr =
| _ -> false
[]
-type SynExprRecordField =
- | SynExprRecordField of
- fieldName: RecordFieldName *
- equalsRange: range option *
- expr: SynExpr option *
- range: range *
- blockSeparator: BlockSeparator option
+type SynTypeSpread = SynTypeSpread of spreadRange: range * ty: SynType * range: range
+
+[]
+type SynExprSpread = SynExprSpread of spreadRange: range * expr: SynExpr * range: range
+
+[]
+type SynExprRecordField = SynExprRecordField of fieldName: RecordFieldName * equalsRange: range option * expr: SynExpr option * range: range
+
+[]
+type SynExprRecordFieldOrSpread =
+ | Field of field: SynExprRecordField * blockSeparator: BlockSeparator option
+ | Spread of spread: SynExprSpread * blockSeparator: BlockSeparator option
+
+[]
+type SynExprAnonRecordField = SynExprAnonRecordField of fieldName: SynLongIdent * equalsRange: range option * expr: SynExpr * range: range
+
+[]
+type SynExprAnonRecordFieldOrSpread =
+ | Field of field: SynExprAnonRecordField * blockSeparator: BlockSeparator option
+ | Spread of spread: SynExprSpread * blockSeparator: BlockSeparator option
+
+ member this.Range =
+ match this with
+ | SynExprAnonRecordFieldOrSpread.Field(SynExprAnonRecordField(_, _, _, m), _)
+ | SynExprAnonRecordFieldOrSpread.Spread(SynExprSpread(_, _, m), _) -> m
[]
type SynInterpolatedStringPart =
| String of value: string * range: range
- | FillExpr of fillExpr: SynExpr * qualifiers: Ident option
+ | FillExpr of fillExpr: SynExpr * formatting: SynInterpolationFormatting
+
+[]
+type SynInterpolationFormatting =
+ | DotNet of alignment: SynExpr option * format: Ident option
+ | Printf of specifier: string * range: range
[]
type SynSimplePat =
@@ -1263,7 +1291,7 @@ type SynTypeDefnSimpleRepr =
| Enum of cases: SynEnumCase list * range: range
- | Record of accessibility: SynAccess option * recordFields: SynField list * range: range
+ | Record of accessibility: SynAccess option * recordFieldsAndSpreads: SynFieldOrSpread list * range: range
| General of
kind: SynTypeDefnKind *
@@ -1296,6 +1324,11 @@ type SynTypeDefnSimpleRepr =
| None(range = m) -> m
| Exception t -> t.Range
+[]
+type SynFieldOrSpread =
+ | Field of field: SynField
+ | Spread of spread: SynTypeSpread
+
[]
type SynEnumCase =
diff --git a/src/Compiler/SyntaxTree/SyntaxTree.fsi b/src/Compiler/SyntaxTree/SyntaxTree.fsi
index 8b152ba2d69..97ca48b425e 100644
--- a/src/Compiler/SyntaxTree/SyntaxTree.fsi
+++ b/src/Compiler/SyntaxTree/SyntaxTree.fsi
@@ -363,6 +363,12 @@ type BlockSeparator = range * pos option
/// correct and can be used in name resolution.
type RecordFieldName = SynLongIdent * bool
+/// Represents either a record field name or a spread expression.
+[]
+type RecordBinding =
+ | Field of name: RecordFieldName * equalsRange: range option * declExpr: SynExpr option
+ | Spread of spread: SynExprSpread
+
/// Indicates if an expression is an atomic expression.
///
/// An atomic expression has no whitespace unless enclosed in parentheses, e.g.
@@ -620,7 +626,7 @@ type SynExpr =
| AnonRecd of
isStruct: bool *
copyInfo: (SynExpr * BlockSeparator) option *
- recordFields: (SynLongIdent * range option * SynExpr) list *
+ recordFields: SynExprAnonRecordFieldOrSpread list *
range: range *
trivia: SynExprAnonRecdTrivia
@@ -634,7 +640,7 @@ type SynExpr =
| Record of
baseInfo: (SynType * SynExpr * range * BlockSeparator option * range) option *
copyInfo: (SynExpr * BlockSeparator) option *
- recordFields: SynExprRecordField list *
+ recordFields: SynExprRecordFieldOrSpread list *
range: range
/// F# syntax: new C(...)
@@ -987,19 +993,57 @@ type SynExpr =
/// Indicates if this expression arises from error recovery
member IsArbExprAndThusAlreadyReportedError: bool
+/// Represents a type spread in a type definition.
+///
+/// type Ty2 = { ...Ty1 }
+[]
+type SynTypeSpread = SynTypeSpread of spreadRange: range * ty: SynType * range: range
+
+/// Represents a spread expression.
+///
+/// ...expr
+[]
+type SynExprSpread = SynExprSpread of spreadRange: range * expr: SynExpr * range: range
+
[]
type SynExprRecordField =
- | SynExprRecordField of
- fieldName: RecordFieldName *
- equalsRange: range option *
- expr: SynExpr option *
- range: range *
- blockSeparator: BlockSeparator option
+ | SynExprRecordField of fieldName: RecordFieldName * equalsRange: range option * expr: SynExpr option * range: range
+
+/// Represents either a field declaration or a spread expression in a nominal record construction expression.
+///
+/// let r = { A = 3; ...b; C = true }
+[]
+type SynExprRecordFieldOrSpread =
+ | Field of field: SynExprRecordField * blockSeparator: BlockSeparator option
+ | Spread of spread: SynExprSpread * blockSeparator: BlockSeparator option
+
+[]
+type SynExprAnonRecordField =
+ | SynExprAnonRecordField of fieldName: SynLongIdent * equalsRange: range option * expr: SynExpr * range: range
+
+/// Represents either a field declaration or a spread expression in an anonymous record construction expression.
+///
+/// let r = {| A = 3; ...b; C = true |}
+[]
+type SynExprAnonRecordFieldOrSpread =
+ | Field of field: SynExprAnonRecordField * blockSeparator: BlockSeparator option
+ | Spread of spread: SynExprSpread * blockSeparator: BlockSeparator option
+
+ member Range: range
[]
type SynInterpolatedStringPart =
| String of value: string * range: range
- | FillExpr of fillExpr: SynExpr * qualifiers: Ident option
+ | FillExpr of fillExpr: SynExpr * formatting: SynInterpolationFormatting
+
+/// Represents how an interpolation hole in an interpolated string is formatted.
+[]
+type SynInterpolationFormatting =
+ /// .NET-style formatting: optional alignment '{x,n}' and optional format '{x:fmt}'.
+ | DotNet of alignment: SynExpr option * format: Ident option
+
+ /// printf-style formatting: a single specifier, the '%d' in '%d{x}'.
+ | Printf of specifier: string * range: range
/// Represents a syntax tree for simple F# patterns
[]
@@ -1379,7 +1423,7 @@ type SynTypeDefnSimpleRepr =
| Enum of cases: SynEnumCase list * range: range
/// A record type definition, type X = { A: int; B: int }
- | Record of accessibility: SynAccess option * recordFields: SynField list * range: range
+ | Record of accessibility: SynAccess option * recordFieldsAndSpreads: SynFieldOrSpread list * range: range
/// An object oriented type definition. This is not a parse-tree form, but represents the core
/// type representation which the type checker splits out from the "ObjectModel" cases of type definitions.
@@ -1412,6 +1456,12 @@ type SynTypeDefnSimpleRepr =
/// Gets the syntax range of this construct
member Range: range
+/// Represents either a field declaration or a type spread.
+[]
+type SynFieldOrSpread =
+ | Field of field: SynField
+ | Spread of spread: SynTypeSpread
+
/// Represents the syntax tree for one case in an enum definition.
[]
type SynEnumCase =
diff --git a/src/Compiler/SyntaxTree/SyntaxTreeOps.fs b/src/Compiler/SyntaxTree/SyntaxTreeOps.fs
index fa30545d480..ffca6718f56 100644
--- a/src/Compiler/SyntaxTree/SyntaxTreeOps.fs
+++ b/src/Compiler/SyntaxTree/SyntaxTreeOps.fs
@@ -823,6 +823,28 @@ let mkSynBinding
let mBind = unionRangeWithXmlDoc xmlDoc mBind
SynBinding(vis, SynBindingKind.Normal, isInline, isMutable, attrs, xmlDoc, info, headPat, retTyOpt, rhsExpr, mBind, spBind, trivia)
+/// A compiler-generated `let!` binding, as produced while desugaring computation expressions: the
+/// usual binding defaults with the leading keyword marked as `let!` at mKeyword.
+let mkSynLetBangBinding mKeyword headPat rhs debugPoint mBind =
+ SynBinding(
+ accessibility = None,
+ kind = SynBindingKind.Normal,
+ isInline = false,
+ isMutable = false,
+ attributes = [],
+ xmlDoc = PreXmlDoc.Empty,
+ valData = SynInfo.emptySynValData,
+ headPat = headPat,
+ returnInfo = None,
+ expr = rhs,
+ range = mBind,
+ debugPoint = debugPoint,
+ trivia =
+ { SynBindingTrivia.Zero with
+ LeadingKeyword = SynLeadingKeyword.LetBang mKeyword
+ }
+ )
+
let NonVirtualMemberFlags k : SynMemberFlags =
{
MemberKind = k
@@ -978,13 +1000,24 @@ let rec synExprContainsError inpExpr =
(match origExpr with
| Some(e, _) -> walkExpr e
| None -> false)
- || walkExprs (List.map (fun (_, _, e) -> e) flds)
+ || walkExprs (
+ List.map
+ (function
+ | SynExprAnonRecordFieldOrSpread.Field(SynExprAnonRecordField(_, _, e, _), _)
+ | SynExprAnonRecordFieldOrSpread.Spread(spread = SynExprSpread(expr = e)) -> e)
+ flds
+ )
| SynExpr.Record(_, origExpr, fs, _) ->
(match origExpr with
| Some(e, _) -> walkExpr e
| None -> false)
- || (let flds = fs |> List.choose (fun (SynExprRecordField(expr = v)) -> v)
+ || (let flds =
+ fs
+ |> List.choose (function
+ | SynExprRecordFieldOrSpread.Field(SynExprRecordField(expr = v), _) -> v
+ | SynExprRecordFieldOrSpread.Spread(SynExprSpread(expr = e), _) -> Some e)
+
walkExprs flds)
| SynExpr.ObjExpr(bindings = bs; members = ms; extraImpls = is) ->
diff --git a/src/Compiler/SyntaxTree/SyntaxTreeOps.fsi b/src/Compiler/SyntaxTree/SyntaxTreeOps.fsi
index 246d661e663..c4915300652 100644
--- a/src/Compiler/SyntaxTree/SyntaxTreeOps.fsi
+++ b/src/Compiler/SyntaxTree/SyntaxTreeOps.fsi
@@ -308,6 +308,9 @@ val mkSynBinding:
trivia: SynBindingTrivia ->
SynBinding
+val mkSynLetBangBinding:
+ mKeyword: range -> headPat: SynPat -> rhs: SynExpr -> debugPoint: DebugPointAtBinding -> mBind: range -> SynBinding
+
val NonVirtualMemberFlags: k: SynMemberKind -> SynMemberFlags
val CtorMemberFlags: SynMemberFlags
diff --git a/src/Compiler/SyntaxTree/UnicodeLexing.fs b/src/Compiler/SyntaxTree/UnicodeLexing.fs
index 4ea41cbcf84..ad6ef32154a 100644
--- a/src/Compiler/SyntaxTree/UnicodeLexing.fs
+++ b/src/Compiler/SyntaxTree/UnicodeLexing.fs
@@ -23,22 +23,21 @@ type LexBuffer<'char> with
| true, data -> Some(data :?> 'T)
| _ -> None
-let StringAsLexbuf (reportLibraryOnlyFeatures, langVersion, strictIndentation, s: string) =
- LexBuffer.FromChars(reportLibraryOnlyFeatures, langVersion, strictIndentation, s.ToCharArray())
+let StringAsLexbuf (reportLibraryOnlyFeatures, langVersion, s: string) =
+ LexBuffer.FromChars(reportLibraryOnlyFeatures, langVersion, s.ToCharArray())
-let FunctionAsLexbuf (reportLibraryOnlyFeatures, langVersion, strictIndentation, bufferFiller) =
- LexBuffer.FromFunction(reportLibraryOnlyFeatures, langVersion, strictIndentation, bufferFiller)
+let FunctionAsLexbuf (reportLibraryOnlyFeatures, langVersion, bufferFiller) =
+ LexBuffer.FromFunction(reportLibraryOnlyFeatures, langVersion, bufferFiller)
-let SourceTextAsLexbuf (reportLibraryOnlyFeatures, langVersion, strictIndentation, sourceText) =
- LexBuffer.FromSourceText(reportLibraryOnlyFeatures, langVersion, strictIndentation, sourceText)
+let SourceTextAsLexbuf (reportLibraryOnlyFeatures, langVersion, sourceText) =
+ LexBuffer.FromSourceText(reportLibraryOnlyFeatures, langVersion, sourceText)
-let StreamReaderAsLexbuf (reportLibraryOnlyFeatures, langVersion, strictIndentation, reader: StreamReader) =
+let StreamReaderAsLexbuf (reportLibraryOnlyFeatures, langVersion, reader: StreamReader) =
let mutable isFinished = false
FunctionAsLexbuf(
reportLibraryOnlyFeatures,
langVersion,
- strictIndentation,
fun (chars, start, length) ->
if isFinished then
0
diff --git a/src/Compiler/SyntaxTree/UnicodeLexing.fsi b/src/Compiler/SyntaxTree/UnicodeLexing.fsi
index ee722ee08c3..e8e3d0b3436 100644
--- a/src/Compiler/SyntaxTree/UnicodeLexing.fsi
+++ b/src/Compiler/SyntaxTree/UnicodeLexing.fsi
@@ -13,27 +13,14 @@ type LexBuffer<'char> with
member GetLocalData<'T when 'T: not null> : key: string * initializer: (unit -> 'T) -> 'T
member TryGetLocalData<'T when 'T: not null> : key: string -> 'T option
-val StringAsLexbuf:
- reportLibraryOnlyFeatures: bool * langVersion: LanguageVersion * strictIndentation: bool option * string -> Lexbuf
+val StringAsLexbuf: reportLibraryOnlyFeatures: bool * langVersion: LanguageVersion * string -> Lexbuf
val FunctionAsLexbuf:
- reportLibraryOnlyFeatures: bool *
- langVersion: LanguageVersion *
- strictIndentation: bool option *
- bufferFiller: (char[] * int * int -> int) ->
- Lexbuf
+ reportLibraryOnlyFeatures: bool * langVersion: LanguageVersion * bufferFiller: (char[] * int * int -> int) -> Lexbuf
val SourceTextAsLexbuf:
- reportLibraryOnlyFeatures: bool *
- langVersion: LanguageVersion *
- strictIndentation: bool option *
- sourceText: ISourceText ->
- Lexbuf
+ reportLibraryOnlyFeatures: bool * langVersion: LanguageVersion * sourceText: ISourceText -> Lexbuf
/// Will not dispose of the stream reader.
val StreamReaderAsLexbuf:
- reportLibraryOnlyFeatures: bool *
- langVersion: LanguageVersion *
- strictIndentation: bool option *
- reader: StreamReader ->
- Lexbuf
+ reportLibraryOnlyFeatures: bool * langVersion: LanguageVersion * reader: StreamReader -> Lexbuf
diff --git a/src/Compiler/TypedTree/CompilerGeneratedNameMapState.fs b/src/Compiler/TypedTree/CompilerGeneratedNameMapState.fs
new file mode 100644
index 00000000000..196ab294e8c
--- /dev/null
+++ b/src/Compiler/TypedTree/CompilerGeneratedNameMapState.fs
@@ -0,0 +1,68 @@
+module internal FSharp.Compiler.CompilerGeneratedNameMapState
+
+open System.Runtime.CompilerServices
+
+/// Minimal abstraction for compiler-generated name replay/state.
+/// Implementations can be hot-reload aware without coupling core compiler paths
+/// to a concrete synthesized-name map type.
+type ICompilerGeneratedNameMap =
+ /// Resets allocation cursors so the next serialized code-generation pass replays the snapshot from its first slot.
+ abstract BeginSession: unit -> unit
+
+ /// Returns the next name in deterministic encounter order for this basic name.
+ /// Consumers must serialize code generation while a map is installed: synchronization prevents data races,
+ /// but concurrent callers cannot make encounter order independent of thread scheduling.
+ abstract GetOrAddName: basicName: string -> string
+
+ /// Captures the names in allocation order, grouped by normalized basic name.
+ abstract Snapshot: seq
+
+ /// Replaces the current replay state with a previously captured allocation-order snapshot.
+ abstract LoadSnapshot: snapshot: seq -> unit
+
+// Keep optional name-map state external to CompilerGlobalState so core signatures can remain stable.
+type private NameMapHolder() =
+ // Reads vastly outnumber writes. Installs happen at most a handful of times per
+ // compile, so the slot is a single volatile field rather than a lock-guarded one.
+ // Reference reads and writes are atomic, and the volatile semantics preserve the
+ // visibility ordering the lock provided.
+ []
+ let mutable current: ICompilerGeneratedNameMap option = None
+
+ member _.TryGet() = current
+ member _.Set(value: ICompilerGeneratedNameMap option) = current <- value
+
+let private holders = ConditionalWeakTable()
+
+let private getOrCreateHolder (owner: obj) =
+ holders.GetValue(owner, fun _ -> NameMapHolder())
+
+/// Pure read: never inserts, so a compile that never installs a map pays a single
+/// failed weak-table lookup.
+let private tryGetHolder (owner: obj) =
+ match holders.TryGetValue owner with
+ | true, holder -> Some holder
+ | _ -> None
+
+let tryGetCompilerGeneratedNameMap (owner: obj) =
+ match tryGetHolder owner with
+ | Some holder -> holder.TryGet()
+ | None -> None
+
+/// A reader for the owner's name-map slot. The holder is resolved exactly once here
+/// and captured by the returned closure, so each generated name costs a single
+/// volatile field read rather than a ConditionalWeakTable probe and lock.
+///
+/// The holder is created eagerly on purpose: the emit hook can install the map later
+/// in the compile, after CompilerGlobalState and therefore this accessor have been
+/// constructed, and it installs through the same owner. Pre-creating the holder means
+/// that later install mutates the object this closure captured, so the map is observed.
+let getCompilerGeneratedNameMapAccessor (owner: obj) : unit -> ICompilerGeneratedNameMap option =
+ let holder = getOrCreateHolder owner
+ fun () -> holder.TryGet()
+
+let setCompilerGeneratedNameMap (owner: obj) (map: ICompilerGeneratedNameMap) = (getOrCreateHolder owner).Set(Some map)
+
+let setCompilerGeneratedNameMapOpt (owner: obj) (map: ICompilerGeneratedNameMap option) = (getOrCreateHolder owner).Set(map)
+
+let clearCompilerGeneratedNameMap (owner: obj) = (getOrCreateHolder owner).Set(None)
diff --git a/src/Compiler/TypedTree/CompilerGlobalState.fs b/src/Compiler/TypedTree/CompilerGlobalState.fs
index dfc8bb0abbe..cd7ccb9a60a 100644
--- a/src/Compiler/TypedTree/CompilerGlobalState.fs
+++ b/src/Compiler/TypedTree/CompilerGlobalState.fs
@@ -8,6 +8,7 @@ open System
open System.Collections.Concurrent
open System.Threading
open Internal.Utilities.Library
+open FSharp.Compiler.CompilerGeneratedNameMapState
open FSharp.Compiler.Syntax.PrettyNaming
open FSharp.Compiler.Text
@@ -18,7 +19,7 @@ open FSharp.Compiler.Text
/// It is made concurrency-safe since a global instance of the type is allocated in tast.fs, and it is good
/// policy to make all globally-allocated objects concurrency safe in case future versions of the compiler
/// are used to host multiple concurrent instances of compilation.
-type NiceNameGenerator() =
+type NiceNameGenerator(getCompilerGeneratedNameMap: unit -> ICompilerGeneratedNameMap option) =
let basicNameCounts = ConcurrentDictionary(max Environment.ProcessorCount 1, 127)
// Cache this as a delegate.
let basicNameCountsAddDelegate = Func(fun _ -> ref 0)
@@ -34,16 +35,37 @@ type NiceNameGenerator() =
CompilerGeneratedNameSuffix basicName (string m.StartLine + (match (count - 1) with 0 -> "" | n -> "-" + string n))
member _.FreshCompilerGeneratedNameOfBasicName (basicName, m: range) =
- let count = increment basicName m
- mkName basicName m count
+ match getCompilerGeneratedNameMap() with
+ | Some map -> map.GetOrAddName basicName
+ | None ->
+ let count = increment basicName m
+ mkName basicName m count
member this.FreshCompilerGeneratedName (name, m: range) =
this.FreshCompilerGeneratedNameOfBasicName (GetBasicNameOfPossibleCompilerGeneratedName name, m)
member _.FreshCompilerGeneratedNameInScope (scopeFileIndex: int, name: string, m: range) =
let basicName = GetBasicNameOfPossibleCompilerGeneratedName name
- let count = incrementBucket basicName scopeFileIndex
- mkName basicName m count
+
+ // The replay map must win over per-file occurrence buckets, exactly as it
+ // does in FreshCompilerGeneratedNameOfBasicName. When a session installs the
+ // map, every allocation path replays the baseline's stable names. Otherwise,
+ // line-based per-file names would drift under edits. The map is only ever
+ // installed by the hot reload emit hook or by an in-process compile, so the
+ // deterministic per-file bucketing from https://github.com/dotnet/fsharp/issues/19732
+ // is untouched in normal compilation.
+ match getCompilerGeneratedNameMap() with
+ | Some map -> map.GetOrAddName basicName
+ | None ->
+ let count = incrementBucket basicName scopeFileIndex
+ mkName basicName m count
+
+ new () = NiceNameGenerator(fun () -> None)
+
+ /// Reset the per-(basicName, file) occurrence counters so a subsequent codegen run assigns the
+ /// same compiler-generated occurrence names a fresh process would. Callers must ensure no
+ /// concurrent codegen is using this generator when resetting.
+ member _.ResetCompilerGeneratedNameState() = basicNameCounts.Clear()
/// Generates compiler-generated names marked up with a source code location, but if given the same unique value then
/// return precisely the same name. Each name generated also includes the StartLine number of the range passed in
@@ -51,31 +73,44 @@ type NiceNameGenerator() =
///
/// This type may be accessed concurrently, though in practice it is only used from the compilation thread.
/// It is made concurrency-safe since a global instance of the type is allocated in tast.fs.
-type StableNiceNameGenerator() =
+type StableNiceNameGenerator(getCompilerGeneratedNameMap: unit -> ICompilerGeneratedNameMap option) =
let niceNames = ConcurrentDictionary>(max Environment.ProcessorCount 1, 127)
- let innerGenerator = NiceNameGenerator()
+ let innerGenerator = NiceNameGenerator(getCompilerGeneratedNameMap)
member x.GetUniqueCompilerGeneratedName (name, m: range, uniq) =
let basicName = GetBasicNameOfPossibleCompilerGeneratedName name
let key = basicName, uniq
niceNames.GetOrAddLazy(key, fun (basicName, _) -> innerGenerator.FreshCompilerGeneratedNameOfBasicName(basicName, m))
+ new () = StableNiceNameGenerator(fun () -> None)
+
+ /// Reset the stable-name cache and inner occurrence counters, so both the cached stable names and
+ /// the underlying occurrence counters are cleared. See NiceNameGenerator.ResetCompilerGeneratedNameState.
+ member _.ResetCompilerGeneratedNameState() =
+ niceNames.Clear()
+ innerGenerator.ResetCompilerGeneratedNameState()
+
[]
type PerFileNamingScope internal (nng: NiceNameGenerator, fileIndex: int) =
member _.Fresh (name: string, m: range) =
nng.FreshCompilerGeneratedNameInScope(fileIndex, name, m)
-type internal CompilerGlobalState () =
+type internal CompilerGlobalState () as this =
+ /// Reader for the optional synthesized-name map attached to this instance. The
+ /// accessor resolves the side-channel slot once, so each generated name costs a
+ /// single None check, not a weak-table probe and lock, when no map is installed.
+ let getCompilerGeneratedNameMap = getCompilerGeneratedNameMapAccessor (this :> obj)
+
/// A global generator of compiler generated names
- let globalNng = NiceNameGenerator()
+ let globalNng = NiceNameGenerator(getCompilerGeneratedNameMap)
/// A global generator of stable compiler generated names
- let globalStableNameGenerator = StableNiceNameGenerator ()
+ let globalStableNameGenerator = StableNiceNameGenerator(getCompilerGeneratedNameMap)
/// A name generator used by IlxGen for static fields, some generated arguments and other things.
- let ilxgenGlobalNng = NiceNameGenerator ()
+ let ilxgenGlobalNng = NiceNameGenerator(getCompilerGeneratedNameMap)
member _.NiceNameGenerator = globalNng
@@ -86,6 +121,15 @@ type internal CompilerGlobalState () =
member _.NewFileScope (fileRange: range) =
PerFileNamingScope(globalNng, fileRange.FileIndex)
+ /// Reset all compiler-generated-name occurrence counters on this state, so successive in-process
+ /// codegen runs over the same source produce identical generated names (a fresh-process layout).
+ /// Callers must ensure no compilation is concurrently generating names (quiescence). Needed by
+ /// Edit-and-Continue style scenarios that re-emit from a warm checker.
+ member _.ResetCompilerGeneratedNameState() =
+ globalNng.ResetCompilerGeneratedNameState()
+ globalStableNameGenerator.ResetCompilerGeneratedNameState()
+ ilxgenGlobalNng.ResetCompilerGeneratedNameState()
+
/// Unique name generator for stamps attached to lambdas and object expressions
type Unique = int64
@@ -98,4 +142,4 @@ let newUnique() = Interlocked.Increment &uniqueCount
let mutable private stampCount = 0L
let newStamp() =
let stamp = Interlocked.Increment &stampCount
- stamp
\ No newline at end of file
+ stamp
diff --git a/src/Compiler/TypedTree/CompilerGlobalState.fsi b/src/Compiler/TypedTree/CompilerGlobalState.fsi
index cf357d066be..5768089a668 100644
--- a/src/Compiler/TypedTree/CompilerGlobalState.fsi
+++ b/src/Compiler/TypedTree/CompilerGlobalState.fsi
@@ -18,6 +18,11 @@ type NiceNameGenerator =
new: unit -> NiceNameGenerator
member FreshCompilerGeneratedName: name: string * m: range -> string
+ /// Reset the per-(basicName, file) occurrence counters so a subsequent codegen run assigns the
+ /// same compiler-generated occurrence names a fresh process would. Callers must ensure no
+ /// concurrent codegen is using this generator when resetting.
+ member ResetCompilerGeneratedNameState: unit -> unit
+
/// Generates compiler-generated names marked up with a source code location, but if given the same unique value then
/// return precisely the same name. Each name generated also includes the StartLine number of the range passed in
/// at the point of first generation.
@@ -29,6 +34,10 @@ type StableNiceNameGenerator =
new: unit -> StableNiceNameGenerator
member GetUniqueCompilerGeneratedName: name: string * m: range * uniq: int64 -> string
+ /// Reset the stable-name cache and inner occurrence counters, so both the cached stable names and
+ /// the underlying occurrence counters are cleared. See NiceNameGenerator.ResetCompilerGeneratedNameState.
+ member ResetCompilerGeneratedNameState: unit -> unit
+
/// A compiler-generated-name allocation scope bound to a single ImplFile being optimized.
/// Instances can only be obtained from CompilerGlobalState.NewFileScope so a call site can't
/// accidentally bucket names by the wrong (e.g. inlined-source) file and reintroduce the
@@ -58,6 +67,12 @@ type internal CompilerGlobalState =
/// under parallel optimization. See https://github.com/dotnet/fsharp/issues/19732.
member NewFileScope: fileRange: range -> PerFileNamingScope
+ /// Reset all compiler-generated-name occurrence counters on this state, so successive in-process
+ /// codegen runs over the same source produce identical generated names (a fresh-process layout).
+ /// Callers must ensure no compilation is concurrently generating names (quiescence). Needed by
+ /// Edit-and-Continue style scenarios that re-emit from a warm checker.
+ member ResetCompilerGeneratedNameState: unit -> unit
+
type Unique = int64
/// Concurrency-safe
diff --git a/src/Compiler/TypedTree/GeneratedNames.fs b/src/Compiler/TypedTree/GeneratedNames.fs
new file mode 100644
index 00000000000..ff09f7bcee9
--- /dev/null
+++ b/src/Compiler/TypedTree/GeneratedNames.fs
@@ -0,0 +1,248 @@
+module internal FSharp.Compiler.GeneratedNames
+
+open System
+open System.Text.RegularExpressions
+
+/// Marker of occurrence-keyed closure class names produced by hot reload closure
+/// name allocation:
+/// `{base}@hotreload#g{generation}_o{occurrenceChain}`. Generation 0 names are minted
+/// by flag-on baseline compiles. Generation N >= 1 names are minted for occurrences
+/// first allocated by a delta compile of session generation N. The `#g..._o...`
+/// suffix space is disjoint from the replayable `-{ordinal}` suffix space of
+/// FSharpSynthesizedTypeMaps, so these names never parse as replay ordinals and are
+/// never produced by sequence replay.
+[]
+let HotReloadGenerationSuffixedNameInfix = "@hotreload#g"
+
+type SynthesizedPositionalName =
+ {
+ NormalizedBasicName: string
+ Ordinal: int list
+ }
+
+type HotReloadReplayName =
+ {
+ NormalizedBasicName: string
+ ReplayOrdinal: int
+ }
+
+type HotReloadGenerationName =
+ {
+ NormalizedBasicName: string
+ Generation: int
+ OccurrenceOrdinal: int list
+ }
+
+let private debugPipeNameRegex =
+ lazy Regex(@"^Pipe #[1-9][0-9]* (?:input|stage #[1-9][0-9]*) at line ([1-9][0-9]*)$", RegexOptions.CultureInvariant)
+
+let private tryParseNonNegativeInt (text: string) =
+ match Int32.TryParse text with
+ | true, value when value >= 0 -> Some value
+ | _ -> None
+
+let private tryParsePositiveInt (text: string) =
+ match Int32.TryParse text with
+ | true, value when value > 0 -> Some value
+ | _ -> None
+
+let private tryParseLineOrdinalSuffix (suffix: string) =
+ let dashIndex = suffix.IndexOf('-')
+
+ if dashIndex < 0 then
+ tryParsePositiveInt suffix |> Option.map (fun line -> line, 0)
+ elif dashIndex > 0 && dashIndex < suffix.Length - 1 then
+ match tryParsePositiveInt (suffix.Substring(0, dashIndex)), tryParseNonNegativeInt (suffix.Substring(dashIndex + 1)) with
+ | Some line, Some ordinal -> Some(line, ordinal)
+ | _ -> None
+ else
+ None
+
+let private tryNormalizeDebugPipeBasicName (name: string) =
+ let matchResult = debugPipeNameRegex.Value.Match name
+
+ if matchResult.Success then
+ match tryParsePositiveInt matchResult.Groups[1].Value with
+ | Some line ->
+ let marker = " at line "
+ let markerIndex = name.LastIndexOf(marker, StringComparison.Ordinal)
+
+ if markerIndex > 0 then
+ Some(name.Substring(0, markerIndex), line)
+ else
+ None
+ | None -> None
+ else
+ None
+
+let private tryParseOccurrenceOrdinal (text: string) =
+ if String.IsNullOrWhiteSpace text then
+ None
+ else
+ let parts = text.Split([| '_' |], StringSplitOptions.None)
+
+ if parts |> Array.exists String.IsNullOrWhiteSpace then
+ None
+ else
+ let parsed = parts |> Array.map tryParseNonNegativeInt
+
+ if parsed |> Array.forall Option.isSome then
+ Some(parsed |> Array.map Option.get |> Array.toList)
+ else
+ None
+
+let private positionalName normalizedBasicName ordinal =
+ {
+ NormalizedBasicName = normalizedBasicName
+ Ordinal = ordinal
+ }
+
+let private tryNormalizeDebugPipeName (name: string) =
+ tryNormalizeDebugPipeBasicName name
+ |> Option.map (fun (normalizedBasicName, line) -> positionalName normalizedBasicName [ line; 0 ])
+
+let TryNormalizeHotReloadGenerationName (name: string) =
+ let markerIndex =
+ name.IndexOf(HotReloadGenerationSuffixedNameInfix, StringComparison.Ordinal)
+
+ if markerIndex <= 0 then
+ None
+ else
+ let baseName = name.Substring(0, markerIndex)
+ let generationStart = markerIndex + HotReloadGenerationSuffixedNameInfix.Length
+ let ordinalMarker = "_o"
+
+ let ordinalMarkerIndex =
+ name.IndexOf(ordinalMarker, generationStart, StringComparison.Ordinal)
+
+ if
+ ordinalMarkerIndex <= generationStart
+ || ordinalMarkerIndex + ordinalMarker.Length >= name.Length
+ || String.IsNullOrWhiteSpace baseName
+ || baseName.IndexOf("@", StringComparison.Ordinal) >= 0
+ then
+ None
+ else
+ match
+ tryParseNonNegativeInt (name.Substring(generationStart, ordinalMarkerIndex - generationStart)),
+ tryParseOccurrenceOrdinal (name.Substring(ordinalMarkerIndex + ordinalMarker.Length))
+ with
+ | Some generation, Some occurrenceOrdinal ->
+ let normalizedBasicName =
+ match tryNormalizeDebugPipeBasicName baseName with
+ | Some(normalizedPipeName, _) -> normalizedPipeName
+ | None -> baseName
+
+ Some
+ {
+ NormalizedBasicName = normalizedBasicName
+ Generation = generation
+ OccurrenceOrdinal = occurrenceOrdinal
+ }
+ | _ -> None
+
+/// Recognizes well-formed occurrence-keyed generation-suffixed closure class names:
+/// `{base}@hotreload#g{N}_o{chain}`, any generation.
+let IsHotReloadGenerationSuffixedName (name: string) =
+ not (String.IsNullOrEmpty name)
+ && (TryNormalizeHotReloadGenerationName name |> Option.isSome)
+
+/// Parses the generation of a well-formed occurrence-keyed closure class name:
+/// `f@hotreload#g2_o3` -> Some 2. None when the name is not generation-suffixed
+/// or any part of the name is malformed.
+let TryGetHotReloadNameGeneration (name: string) : int option =
+ if String.IsNullOrEmpty name then
+ None
+ else
+ TryNormalizeHotReloadGenerationName name |> Option.map _.Generation
+
+let TryNormalizeHotReloadReplayName (name: string) =
+ let marker = "@hotreload"
+ let markerIndex = name.LastIndexOf(marker, StringComparison.Ordinal)
+
+ if markerIndex <= 0 then
+ None
+ else
+ let suffixStart = markerIndex + marker.Length
+ let suffix = name.Substring suffixStart
+ let baseName = name.Substring(0, markerIndex)
+
+ if
+ String.IsNullOrWhiteSpace baseName
+ || baseName.IndexOf("@", StringComparison.Ordinal) >= 0
+ then
+ None
+ else
+ let ordinalOpt =
+ if suffix = "" then
+ Some 0
+ elif suffix.StartsWith("-", StringComparison.Ordinal) then
+ tryParsePositiveInt (suffix.Substring 1)
+ else
+ None
+
+ ordinalOpt
+ |> Option.map (fun ordinal ->
+ let normalizedBasicName =
+ match tryNormalizeDebugPipeBasicName baseName with
+ | Some(normalizedPipeName, _) -> normalizedPipeName
+ | None -> baseName
+
+ {
+ NormalizedBasicName = normalizedBasicName
+ ReplayOrdinal = ordinal
+ })
+
+let private tryNormalizeHotReloadOrdinalName (name: string) =
+ TryNormalizeHotReloadReplayName name
+ |> Option.map (fun replayName ->
+ let ordinal =
+ let markerIndex = name.LastIndexOf("@hotreload", StringComparison.Ordinal)
+ let baseName = name.Substring(0, markerIndex)
+
+ match tryNormalizeDebugPipeBasicName baseName with
+ | Some(_, line) -> [ line; replayName.ReplayOrdinal ]
+ | None -> [ replayName.ReplayOrdinal ]
+
+ positionalName replayName.NormalizedBasicName ordinal)
+
+let private tryNormalizeLineOrdinalName (name: string) =
+ let atIndex = name.LastIndexOf('@')
+
+ if atIndex <= 0 || atIndex = name.Length - 1 then
+ None
+ else
+ let baseName = name.Substring(0, atIndex)
+ let suffix = name.Substring(atIndex + 1)
+
+ match tryParseLineOrdinalSuffix suffix with
+ | None -> None
+ | Some(line, ordinal) ->
+ match tryNormalizeDebugPipeBasicName baseName with
+ | Some(normalizedPipeName, pipeLine) when pipeLine = line -> Some(positionalName normalizedPipeName [ line; ordinal ])
+ | Some _ -> None
+ | None ->
+ if
+ String.IsNullOrWhiteSpace baseName
+ || baseName.IndexOf("@", StringComparison.Ordinal) >= 0
+ || baseName.StartsWith("Pipe #", StringComparison.Ordinal)
+ then
+ None
+ else
+ Some(positionalName baseName [ line; ordinal ])
+
+let tryNormalizeSynthesizedTypeNameForPositionalPairing (name: string) =
+ if String.IsNullOrWhiteSpace name then
+ None
+ else
+ match tryNormalizeHotReloadOrdinalName name with
+ | Some normalized -> Some normalized
+ | None ->
+ match tryNormalizeLineOrdinalName name with
+ | Some normalized -> Some normalized
+ | None -> tryNormalizeDebugPipeName name
+
+let SynthesizedNameMapKey (basicName: string) =
+ match tryNormalizeSynthesizedTypeNameForPositionalPairing basicName with
+ | Some normalized -> normalized.NormalizedBasicName
+ | None -> basicName
diff --git a/src/Compiler/TypedTree/SynthesizedTypeMaps.fs b/src/Compiler/TypedTree/SynthesizedTypeMaps.fs
new file mode 100644
index 00000000000..3af9e23add4
--- /dev/null
+++ b/src/Compiler/TypedTree/SynthesizedTypeMaps.fs
@@ -0,0 +1,266 @@
+module internal FSharp.Compiler.SynthesizedTypeMaps
+
+open System
+open System.Collections.Generic
+
+open FSharp.Compiler.CompilerGeneratedNameMapState
+open FSharp.Compiler.GeneratedNames
+open FSharp.Compiler.Syntax.PrettyNaming
+
+///
+/// Provides stable compiler-generated names across hot reload sessions.
+///
+/// Replay buckets are keyed by line-normalized basic name. Bucket values remain the
+/// original generation-0 full names, so a matched closure whose code moves from line
+/// 28 to line 30 still gets its line-28 birth name back. That mirrors Roslyn EnC:
+/// identity is established at first allocation and replayed exactly.
+///
+type FSharpSynthesizedTypeMaps() =
+ let syncLock = obj ()
+ // Every access is protected by syncLock so allocation order and bucket updates stay atomic.
+ let buckets = Dictionary>(StringComparer.Ordinal)
+ let ordinals = Dictionary(StringComparer.Ordinal)
+ let mutable usesRecordedSnapshot = false
+
+ let makeHotReloadName (baseName: string) ordinal =
+ let suffix = if ordinal <= 0 then "hotreload" else $"hotreload-{ordinal}"
+
+ CompilerGeneratedNameSuffix baseName suffix
+
+ let createBucket (names: string[]) =
+ let bucket = ResizeArray()
+
+ for name in names do
+ bucket.Add(name)
+
+ bucket
+
+ let computeName basicName index = makeHotReloadName basicName index
+
+ let getOrAddBucket mapKey =
+ match buckets.TryGetValue mapKey with
+ | true, bucket -> bucket
+ | _ ->
+ let bucket = ResizeArray()
+ buckets.Add(mapKey, bucket)
+ bucket
+
+ let tryGetHotReloadOrdinal (mapKey: string) (name: string) =
+ match TryNormalizeHotReloadReplayName name with
+ | Some replayName when replayName.NormalizedBasicName = mapKey -> Some replayName.ReplayOrdinal
+ | _ -> None
+
+ let tryGetStableOrdinal (mapKey: string) (name: string) =
+ match TryNormalizeHotReloadReplayName name with
+ | Some replayName when replayName.NormalizedBasicName = mapKey -> Some [ replayName.ReplayOrdinal ]
+ | _ ->
+ match TryNormalizeHotReloadGenerationName name with
+ | Some generationName when generationName.NormalizedBasicName = mapKey -> Some generationName.OccurrenceOrdinal
+ | _ -> None
+
+ let canonicalizeSnapshotNames mapKey (names: string[]) =
+ let parsed =
+ names
+ |> Array.mapi (fun index name -> index, name, tryGetHotReloadOrdinal mapKey name)
+
+ if parsed |> Array.forall (fun (_, _, ordinalOpt) -> ordinalOpt.IsSome) then
+ // IL metadata can enumerate synthesized helpers in a different order than allocation.
+ // Normalize pure hot-reload buckets so replay always starts at ordinal 0, then 1, etc.
+ let sorted =
+ parsed
+ |> Array.sortBy (fun (index, _, ordinalOpt) -> struct (ordinalOpt.Value, index))
+
+ let ordinalsAreDistinct =
+ let ordinals = sorted |> Array.map (fun (_, _, ordinalOpt) -> ordinalOpt.Value)
+ (Array.distinct ordinals).Length = ordinals.Length
+
+ if ordinalsAreDistinct && sorted.Length > 0 then
+ // Place every name at the slot index its ordinal records, filling holes
+ // with the computed name for that slot. Holes arise exactly where an
+ // allocation's replay name never surfaced in IL. The filler equals what
+ // GetOrAddName produced for that slot originally, so replay positions
+ // are exact.
+ let maxOrdinal =
+ sorted |> Array.map (fun (_, _, ordinalOpt) -> ordinalOpt.Value) |> Array.max
+
+ let namesByOrdinal =
+ sorted
+ |> Array.map (fun (_, name, ordinalOpt) -> ordinalOpt.Value, name)
+ |> Map.ofArray
+
+ let replayFillBasicName =
+ let rawBasicNames =
+ sorted
+ |> Array.choose (fun (_, name, _) ->
+ let rawBasicName = GetBasicNameOfPossibleCompilerGeneratedName name
+
+ if String.Equals(SynthesizedNameMapKey rawBasicName, mapKey, StringComparison.Ordinal) then
+ Some rawBasicName
+ else
+ None)
+ |> Array.distinct
+
+ match rawBasicNames with
+ | [| rawBasicName |] -> rawBasicName
+ | _ -> mapKey
+
+ Array.init (maxOrdinal + 1) (fun slot ->
+ match Map.tryFind slot namesByOrdinal with
+ | Some name -> name
+ | None -> makeHotReloadName replayFillBasicName slot)
+ else
+ sorted |> Array.map (fun (_, name, _) -> name)
+ else
+ let parsed =
+ names
+ |> Array.mapi (fun index name -> index, name, tryGetStableOrdinal mapKey name)
+
+ if parsed |> Array.forall (fun (_, _, ordinalOpt) -> ordinalOpt.IsSome) then
+ let sorted =
+ parsed
+ |> Array.sortBy (fun (index, _, ordinalOpt) -> struct (ordinalOpt.Value, index))
+
+ let ordinalsAreDistinct =
+ let ordinals = sorted |> Array.map (fun (_, _, ordinalOpt) -> ordinalOpt.Value)
+ (Array.distinct ordinals).Length = ordinals.Length
+
+ if ordinalsAreDistinct then
+ sorted |> Array.map (fun (_, name, _) -> name)
+ else
+ names
+ else
+ names
+
+ let nameMapKeyFromSnapshotName (name: string) =
+ GetBasicNameOfPossibleCompilerGeneratedName name |> SynthesizedNameMapKey
+
+ /// Validates that a generated name belongs to the normalized map key.
+ let validateName mapKey (name: string) index =
+ // Snapshots can contain legacy/basic synthesized names, for example
+ // "@_instance", alongside hot-reload-managed names. Accept both forms so
+ // existing sessions restore.
+ let actualKey = nameMapKeyFromSnapshotName name
+
+ if not (String.Equals(actualKey, mapKey, StringComparison.Ordinal)) then
+ invalidArg "snapshot" $"Name '{name}' at index {index} belongs to normalized key '{actualKey}', not snapshot key '{mapKey}'"
+
+ let loadSnapshotCore canonicalize (snapshot: seq) =
+ lock syncLock (fun () ->
+ buckets.Clear()
+ ordinals.Clear()
+ usesRecordedSnapshot <- not canonicalize
+
+ let normalizedBuckets =
+ Dictionary>(StringComparer.Ordinal)
+
+ for struct (basicName, names) in snapshot do
+ let mapKey = SynthesizedNameMapKey basicName
+
+ if canonicalize then
+ // Validate each name matches the normalized key. Loading normalizes
+ // old raw-key snapshots, so on-disk baselines captured before this
+ // change replay through the same line-stable buckets.
+ names |> Array.iteri (fun i name -> validateName mapKey name i)
+ else
+ // Recorded snapshots are allocation-key to final-emitted-name slots.
+ // Occurrence-keyed closure overrides can intentionally move a final
+ // name into a bucket whose allocation key differs from the name's
+ // derived key, so only null validation applies here.
+ names
+ |> Array.iteri (fun i name ->
+ if isNull (box name) then
+ invalidArg "snapshot" $"Name at index {i} in snapshot key '{mapKey}' is null")
+
+ let namesToLoad =
+ if canonicalize then
+ canonicalizeSnapshotNames mapKey names
+ else
+ // Recorded snapshots are already in allocation order. Keep them
+ // identity-preserving after validation. Old reconstructed
+ // snapshots continue through canonicalization.
+ Array.copy names
+
+ let bucket =
+ match normalizedBuckets.TryGetValue mapKey with
+ | true, existing -> existing
+ | _ ->
+ let created = ResizeArray()
+ normalizedBuckets[mapKey] <- created
+ created
+
+ for name in namesToLoad do
+ if canonicalize then
+ if not (bucket.Contains name) then
+ bucket.Add name
+ else
+ bucket.Add name
+
+ for KeyValue(mapKey, bucket) in normalizedBuckets do
+ buckets[mapKey] <- createBucket (bucket.ToArray())
+ ordinals[mapKey] <- 0)
+
+ member _.GetOrAddName(basicName: string) =
+ lock syncLock (fun () ->
+ let mapKey = SynthesizedNameMapKey basicName
+ let bucket = getOrAddBucket mapKey
+
+ // Keep ordinal reservation and bucket mutation in one critical section so
+ // concurrent callers cannot observe or produce out-of-order allocations.
+ // The ordinal is intentionally the encounter order within the normalized
+ // bucket. If same-bucket closures are reordered, the downstream
+ // positional-pairing shape guard owns that concern. This allocator only
+ // replays generation-0 names for matching allocation slots.
+ let index =
+ match ordinals.TryGetValue mapKey with
+ | true, current ->
+ ordinals[mapKey] <- current + 1
+ current
+ | _ ->
+ ordinals[mapKey] <- 1
+ 0
+
+ if index < bucket.Count then
+ bucket[index]
+ else
+ let name = computeName basicName index
+ bucket.Add name
+ name)
+
+ /// Resets allocation state so subsequent edits reuse the original name ordering.
+ member _.BeginSession() =
+ lock syncLock (fun () ->
+ for KeyValue(key, _) in buckets do
+ ordinals[key] <- 0)
+
+ /// Captures the current stable names grouped by compiler-generated base name.
+ member _.Snapshot: seq =
+ lock syncLock (fun () ->
+ buckets
+ |> Seq.map (fun (KeyValue(key, bucket)) -> struct (key, bucket.ToArray()))
+ |> Seq.sortWith (fun struct (left, _) struct (right, _) -> StringComparer.Ordinal.Compare(left, right))
+ |> Seq.toArray
+ :> seq)
+
+ member _.UsesRecordedSnapshot = lock syncLock (fun () -> usesRecordedSnapshot)
+
+ /// Loads a previously captured snapshot, replacing any existing allocation state.
+ member _.LoadSnapshot(snapshot: seq) = loadSnapshotCore true snapshot
+
+ ///
+ /// Loads a snapshot that was recorded from this allocator's own allocation slots.
+ /// The bucket arrays are ground truth, so this intentionally skips IL-order
+ /// reconstruction canonicalization and key-derived name validation.
+ ///
+ member _.LoadRecordedSnapshot(snapshot: seq) = loadSnapshotCore false snapshot
+
+ interface ICompilerGeneratedNameMap with
+ member this.BeginSession() = this.BeginSession()
+ member this.GetOrAddName(basicName) = this.GetOrAddName(basicName)
+ member this.Snapshot = this.Snapshot
+ member this.LoadSnapshot(snapshot) = this.LoadSnapshot(snapshot)
+
+/// Retrieves a stable compiler-generated name or falls back to the provided generator.
+let nextName (mapOpt: ICompilerGeneratedNameMap option) basicName generate =
+ match mapOpt with
+ | Some map -> map.GetOrAddName basicName
+ | None -> generate ()
diff --git a/src/Compiler/TypedTree/TcGlobals.fs b/src/Compiler/TypedTree/TcGlobals.fs
index 237ec492651..3f983633574 100644
--- a/src/Compiler/TypedTree/TcGlobals.fs
+++ b/src/Compiler/TypedTree/TcGlobals.fs
@@ -806,6 +806,7 @@ type TcGlobals(
let v_byte_operator_info = makeIntrinsicValRef(fslib_MFOperators_nleref, "byte" , None , Some "ToByte", [vara], ([[varaTy]], v_byte_ty))
let v_sbyte_operator_info = makeIntrinsicValRef(fslib_MFOperators_nleref, "sbyte" , None , Some "ToSByte", [vara], ([[varaTy]], v_sbyte_ty))
+ let v_string_operator_info = makeIntrinsicValRef(fslib_MFOperators_nleref, "string" , None , Some "ToString", [vara], ([[varaTy]], v_string_ty))
let v_int16_operator_info = makeIntrinsicValRef(fslib_MFOperators_nleref, "int16" , None , Some "ToInt16", [vara], ([[varaTy]], v_int16_ty))
let v_uint16_operator_info = makeIntrinsicValRef(fslib_MFOperators_nleref, "uint16" , None , Some "ToUInt16", [vara], ([[varaTy]], v_uint16_ty))
let v_int32_operator_info = makeIntrinsicValRef(fslib_MFOperators_nleref, "int32" , None , Some "ToInt32", [vara], ([[varaTy]], v_int32_ty))
@@ -1610,6 +1611,7 @@ type TcGlobals(
member _.byte_operator_info = v_byte_operator_info
member _.sbyte_operator_info = v_sbyte_operator_info
+ member _.string_operator_info = v_string_operator_info
member _.int16_operator_info = v_int16_operator_info
member _.uint16_operator_info = v_uint16_operator_info
member _.int32_operator_info = v_int32_operator_info
@@ -1723,7 +1725,6 @@ type TcGlobals(
member _.seq_map_info = v_seq_map_info
member _.seq_singleton_info = v_seq_singleton_info
member _.seq_empty_info = v_seq_empty_info
- member _.sprintf_info = v_sprintf_info
member _.new_format_info = v_new_format_info
member _.unbox_info = v_unbox_info
member _.get_generic_comparer_info = v_get_generic_comparer_info
diff --git a/src/Compiler/TypedTree/TcGlobals.fsi b/src/Compiler/TypedTree/TcGlobals.fsi
index 214ad0d17cd..709abfc5b18 100644
--- a/src/Compiler/TypedTree/TcGlobals.fsi
+++ b/src/Compiler/TypedTree/TcGlobals.fsi
@@ -941,6 +941,8 @@ type internal TcGlobals =
member sbyte_operator_info: IntrinsicValRef
+ member string_operator_info: IntrinsicValRef
+
member sbyte_tcr: TypedTree.EntityRef
member sbyte_ty: TypedTree.TType
@@ -1005,8 +1007,6 @@ type internal TcGlobals =
member splice_raw_expr_vref: TypedTree.ValRef
- member sprintf_info: IntrinsicValRef
-
member sprintf_vref: TypedTree.ValRef
member string_ty: TypedTree.TType
diff --git a/src/Compiler/TypedTree/TypedTreeOps.Attributes.fs b/src/Compiler/TypedTree/TypedTreeOps.Attributes.fs
index dd2b7cebe14..8eb82ec2639 100644
--- a/src/Compiler/TypedTree/TypedTreeOps.Attributes.fs
+++ b/src/Compiler/TypedTree/TypedTreeOps.Attributes.fs
@@ -183,6 +183,7 @@ module internal ILExtensions =
WellKnownILAttributes.SetsRequiredMembersAttribute
| "System.ObsoleteAttribute" -> WellKnownILAttributes.ObsoleteAttribute
| "System.Diagnostics.CodeAnalysis.ExperimentalAttribute" -> WellKnownILAttributes.ExperimentalAttribute
+ | "System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute" -> WellKnownILAttributes.NotNullIfNotNullAttribute
| "System.AttributeUsageAttribute" -> WellKnownILAttributes.AttributeUsageAttribute
| _ -> WellKnownILAttributes.None
@@ -592,6 +593,11 @@ module internal AttributeHelpers =
| "ConditionalAttribute" -> WellKnownValAttributes.ConditionalAttribute
| _ -> WellKnownValAttributes.None
+ | [| "System"; "Diagnostics"; "CodeAnalysis"; name |] ->
+ match name with
+ | "NotNullIfNotNullAttribute" -> WellKnownValAttributes.NotNullIfNotNullAttribute
+ | _ -> WellKnownValAttributes.None
+
| [| "System"; name |] ->
match name with
| "ThreadStaticAttribute" -> WellKnownValAttributes.ThreadStaticAttribute
diff --git a/src/Compiler/TypedTree/TypedTreeOps.ExprOps.fs b/src/Compiler/TypedTree/TypedTreeOps.ExprOps.fs
index 91ed02ee1a3..0d74adb8be2 100644
--- a/src/Compiler/TypedTree/TypedTreeOps.ExprOps.fs
+++ b/src/Compiler/TypedTree/TypedTreeOps.ExprOps.fs
@@ -1368,6 +1368,9 @@ module internal Makers =
let mkCallNewFormat (g: TcGlobals) m aty bty cty dty ety formatStringExpr =
mkApps g (typedExprForIntrinsic g m g.new_format_info, [ [ aty; bty; cty; dty; ety ] ], [ formatStringExpr ], m)
+ let mkCallStringOperator (g: TcGlobals) m argTy e =
+ mkApps g (typedExprForIntrinsic g m g.string_operator_info, [ [ argTy ] ], [ e ], m)
+
let tryMkCallBuiltInWitness (g: TcGlobals) traitInfo argExprs m =
let info, tinst = g.MakeBuiltInWitnessInfo traitInfo
let vref = ValRefForIntrinsic info
@@ -1451,9 +1454,6 @@ module internal Makers =
let mkCallSeqEmpty g m ty1 =
mkApps g (typedExprForIntrinsic g m g.seq_empty_info, [ [ ty1 ] ], [], m)
- let mkCall_sprintf (g: TcGlobals) m funcTy fmtExpr fillExprs =
- mkApps g (typedExprForIntrinsic g m g.sprintf_info, [ [ funcTy ] ], fmtExpr :: fillExprs, m)
-
let mkCallDeserializeQuotationFSharp20Plus g m e1 e2 e3 e4 =
let args = [ e1; e2; e3; e4 ]
mkApps g (typedExprForIntrinsic g m g.deserialize_quoted_FSharp_20_plus_info, [], [ mkRefTupledNoTypes g m args ], m)
@@ -1572,6 +1572,17 @@ module internal Makers =
m
)
+ /// Concatenate string-valued expressions, choosing the cheapest String.Concat overload by arity.
+ /// An empty list yields "" and a singleton yields itself.
+ let mkStringConcat (g: TcGlobals, m: range, exprs: Expr list) =
+ match exprs with
+ | [] -> mkString g m ""
+ | [ arg ] -> arg
+ | [ arg1; arg2 ] -> mkStaticCall_String_Concat2 g m arg1 arg2
+ | [ arg1; arg2; arg3 ] -> mkStaticCall_String_Concat3 g m arg1 arg2 arg3
+ | [ arg1; arg2; arg3; arg4 ] -> mkStaticCall_String_Concat4 g m arg1 arg2 arg3 arg4
+ | _ -> mkStaticCall_String_Concat_Array g m (mkArray (g.string_ty, exprs, m))
+
// Quotations can't contain any IL.
// As a result, we aim to get rid of all IL generation in the typechecker and pattern match
// compiler, or else train the quotation generator to understand the generated IL.
diff --git a/src/Compiler/TypedTree/TypedTreeOps.ExprOps.fsi b/src/Compiler/TypedTree/TypedTreeOps.ExprOps.fsi
index ad90c5c818c..cce19a8e556 100644
--- a/src/Compiler/TypedTree/TypedTreeOps.ExprOps.fsi
+++ b/src/Compiler/TypedTree/TypedTreeOps.ExprOps.fsi
@@ -208,6 +208,9 @@ module internal Makers =
val mkCallNewFormat:
TcGlobals -> range -> TType -> TType -> TType -> TType -> TType -> formatStringExpr: Expr -> Expr
+ /// Build a call to the 'string' operator (Operators.ToString) at the given argument type.
+ val mkCallStringOperator: TcGlobals -> range -> argTy: TType -> Expr -> Expr
+
val mkCallGetGenericComparer: TcGlobals -> range -> Expr
val mkCallGetGenericEREqualityComparer: TcGlobals -> range -> Expr
@@ -401,9 +404,6 @@ module internal Makers =
val mkCallSeqEmpty: TcGlobals -> range -> TType -> Expr
- /// Make a call to the 'isprintf' function for string interpolation
- val mkCall_sprintf: g: TcGlobals -> m: range -> funcTy: TType -> fmtExpr: Expr -> fillExprs: Expr list -> Expr
-
val mkCallDeserializeQuotationFSharp20Plus: TcGlobals -> range -> Expr -> Expr -> Expr -> Expr -> Expr
val mkCallDeserializeQuotationFSharp40Plus: TcGlobals -> range -> Expr -> Expr -> Expr -> Expr -> Expr -> Expr
@@ -446,6 +446,10 @@ module internal Makers =
val mkStaticCall_String_Concat_Array: TcGlobals -> range -> Expr -> Expr
+ /// Concatenate string-valued expressions, choosing the cheapest String.Concat overload by arity.
+ /// An empty list yields "" and a singleton yields itself.
+ val mkStringConcat: TcGlobals * range * Expr list -> Expr
+
val mkDecr: TcGlobals -> range -> Expr -> Expr
val mkIncr: TcGlobals -> range -> Expr -> Expr
diff --git a/src/Compiler/TypedTree/WellKnownAttribs.fs b/src/Compiler/TypedTree/WellKnownAttribs.fs
index fac3508a56e..748f525b89c 100644
--- a/src/Compiler/TypedTree/WellKnownAttribs.fs
+++ b/src/Compiler/TypedTree/WellKnownAttribs.fs
@@ -116,6 +116,7 @@ type internal WellKnownValAttributes =
| NoEagerConstraintApplicationAttribute = (1uL <<< 38)
| ValueAsStaticPropertyAttribute = (1uL <<< 39)
| TailCallAttribute = (1uL <<< 40)
+ | NotNullIfNotNullAttribute = (1uL <<< 41)
| NotComputed = (1uL <<< 63)
module internal Flags =
diff --git a/src/Compiler/TypedTree/WellKnownAttribs.fsi b/src/Compiler/TypedTree/WellKnownAttribs.fsi
index da7a7b67f33..4939f94aaa8 100644
--- a/src/Compiler/TypedTree/WellKnownAttribs.fsi
+++ b/src/Compiler/TypedTree/WellKnownAttribs.fsi
@@ -114,6 +114,7 @@ type internal WellKnownValAttributes =
| NoEagerConstraintApplicationAttribute = (1uL <<< 38)
| ValueAsStaticPropertyAttribute = (1uL <<< 39)
| TailCallAttribute = (1uL <<< 40)
+ | NotNullIfNotNullAttribute = (1uL <<< 41)
| NotComputed = (1uL <<< 63)
module internal Flags =
diff --git a/src/Compiler/Utilities/Caches.fs b/src/Compiler/Utilities/Caches.fs
index fb024844e09..d8c143cebc6 100644
--- a/src/Compiler/Utilities/Caches.fs
+++ b/src/Compiler/Utilities/Caches.fs
@@ -22,14 +22,12 @@ module CacheMetrics =
let creations = Meter.CreateCounter("creations", "count")
let disposals = Meter.CreateCounter("disposals", "count")
- let mutable private nextCacheId = 0
-
let mkTags (name: string) =
- let cacheId = Interlocked.Increment &nextCacheId
// Avoid TagList(ReadOnlySpan<...>) to support net472 runtime
+ // Only the cache name is tagged: a per-instance id would be published on every measurement,
+ // inflating the tag payload sent to any connected exporter for no in-process benefit.
let mutable tags = TagList()
tags.Add("name", box name)
- tags.Add("cacheId", box cacheId)
tags
let Add (tags: inref) = adds.Add(1L, &tags)
@@ -78,6 +76,10 @@ module CacheMetrics =
let getStatsByName name =
statsByName.GetOrAdd(name, fun _ -> Stats())
+ let getTotalsByName name = (getStatsByName name).GetTotals()
+
+ let getRatioByName name = (getStatsByName name).Ratio
+
let ListenToAll () =
let listener = new MeterListener()
@@ -123,50 +125,6 @@ module CacheMetrics =
Console.WriteLine(StatsToString())
}
- []
- type CacheMetricsListener(cacheTags: TagList, ?nameOnlyFilter: string) =
-
- let stats = Stats()
- let listener = new MeterListener()
-
- do
- for instrument in allCounters do
- listener.EnableMeasurementEvents instrument
-
- listener.SetMeasurementEventCallback(fun instrument v tags _ ->
- let shouldIncrement =
- match nameOnlyFilter with
- | Some filterName ->
- match tags[0].Value with
- | :? string as name when name = filterName -> true
- | _ -> false
- | None -> tags[0] = cacheTags[0] && tags[1] = cacheTags[1]
-
- if shouldIncrement then
- stats.Incr instrument.Name v)
-
- listener.Start()
-
- /// Creates a listener that aggregates metrics across all cache instances with the given name.
- new(cacheName: string) = new CacheMetricsListener(TagList(), nameOnlyFilter = cacheName)
-
- interface IDisposable with
- member _.Dispose() = listener.Dispose()
-
- /// Gets the current totals for each metric type.
- member _.GetTotals() = stats.GetTotals()
-
- /// Gets the current hit ratio (hits / (hits + misses)).
- member _.Ratio = stats.Ratio
-
- /// Gets the total number of cache hits.
- member _.Hits = stats.GetTotals().[hits.Name]
-
- /// Gets the total number of cache misses.
- member _.Misses = stats.GetTotals().[misses.Name]
-
- override _.ToString() = stats.ToString()
-
[]
type EvictionMode =
| NoEviction
@@ -361,10 +319,6 @@ type Cache<'Key, 'Value when 'Key: not null> internal (options: CacheOptions<'Ke
post, dispose
-#if DEBUG
- let debugListener = new CacheMetrics.CacheMetricsListener(tags)
-#endif
-
do CacheMetrics.Created &tags
member val Evicted = evicted.Publish
@@ -430,9 +384,6 @@ type Cache<'Key, 'Value when 'Key: not null> internal (options: CacheOptions<'Ke
CacheMetrics.Update &tags
post (EvictionQueueMessage.Update result)
- member _.CreateMetricsListener() =
- new CacheMetrics.CacheMetricsListener(tags)
-
member _.Dispose() =
if Interlocked.Exchange(&disposed, 1) = 0 then
disposeEvictionProcessor ()
@@ -447,5 +398,8 @@ type Cache<'Key, 'Value when 'Key: not null> internal (options: CacheOptions<'Ke
override this.Finalize() = this.Dispose()
#if DEBUG
- member _.DebugDisplay() = debugListener.ToString()
+ // Shows the totals aggregated for this cache's name. Populated only while a metrics listener
+ // (CacheMetrics.ListenToAll, e.g. under --times or the editor's metrics view) is running.
+ member _.DebugDisplay() =
+ (CacheMetrics.getStatsByName name).ToString()
#endif
diff --git a/src/Compiler/Utilities/Caches.fsi b/src/Compiler/Utilities/Caches.fsi
index 3e1c98e9bb1..e0bff618fcb 100644
--- a/src/Compiler/Utilities/Caches.fsi
+++ b/src/Compiler/Utilities/Caches.fsi
@@ -8,25 +8,18 @@ module CacheMetrics =
/// Global telemetry Meter for all caches. Exposed for testing purposes.
/// Set FSHARP_OTEL_EXPORT environment variable to enable OpenTelemetry export to external collectors in tests.
val Meter: Meter
+
+ /// Current metric totals aggregated across all cache instances with the given name.
+ /// Totals only accumulate while a listener from ListenToAll is running.
+ val internal getTotalsByName: name: string -> Map
+
+ /// Current hit ratio (hits / (hits + misses)) aggregated across all cache instances with the given name.
+ val internal getRatioByName: name: string -> float
+
val internal ListenToAll: unit -> IDisposable
val internal StatsToString: unit -> string
val internal CaptureStatsAndWriteToConsole: unit -> IDisposable
- /// A listener that captures cache metrics, matching by cache name or exact cache tags.
- []
- type CacheMetricsListener =
- /// Creates a listener that aggregates metrics across all cache instances with the given name.
- new: cacheName: string -> CacheMetricsListener
- /// Gets the current totals for each metric type.
- member GetTotals: unit -> Map
- /// Gets the current hit ratio (hits / (hits + misses)).
- member Ratio: float
- /// Gets the total number of cache hits.
- member Hits: int64
- /// Gets the total number of cache misses.
- member Misses: int64
- interface IDisposable
-
[]
type internal EvictionMode =
/// Do not evict items, cache is effectively a ConcurrentDictionary.
@@ -74,5 +67,3 @@ type internal Cache<'Key, 'Value when 'Key: not null> =
member Evicted: IEvent
/// For testing only.
member EvictionFailed: IEvent
- /// For testing only. Creates a local telemetry listener for this cache instance.
- member CreateMetricsListener: unit -> CacheMetrics.CacheMetricsListener
diff --git a/src/Compiler/Utilities/range.fs b/src/Compiler/Utilities/range.fs
index 3a22199c32f..2a05fa74c75 100755
--- a/src/Compiler/Utilities/range.fs
+++ b/src/Compiler/Utilities/range.fs
@@ -334,7 +334,7 @@ type Range(code1: int64, code2: int64) =
member m.FileName = fileOfFileIndex m.FileIndex
member internal m.ShortFileName =
- Path.GetFileName(fileOfFileIndex m.FileIndex) |> nonNull
+ Path.GetFileName(fileOfFileIndex m.FileIndex) |> Unchecked.nonNull
member m.ApplyLineDirectives() =
match LineDirectives.store.TryFind m.FileIndex with
diff --git a/src/Compiler/lex.fsl b/src/Compiler/lex.fsl
index ce4dd5955a6..32d1a39acde 100644
--- a/src/Compiler/lex.fsl
+++ b/src/Compiler/lex.fsl
@@ -201,8 +201,8 @@ let shouldStartFile args lexbuf (m:range) err tok =
if (m.StartColumn <> 0 || m.StartLine <> 1) then fail args lexbuf err tok
else tok
-let evalIfDefExpression startPos reportLibraryOnlyFeatures langVersion strictIndentation args (lookup: string -> bool) (lexed: string) =
- let lexbuf = LexBuffer.FromChars (reportLibraryOnlyFeatures, langVersion, strictIndentation, lexed.ToCharArray ())
+let evalIfDefExpression startPos reportLibraryOnlyFeatures langVersion args (lookup: string -> bool) (lexed: string) =
+ let lexbuf = LexBuffer.FromChars (reportLibraryOnlyFeatures, langVersion, lexed.ToCharArray ())
lexbuf.StartPos <- startPos
lexbuf.EndPos <- startPos
let tokenStream = FSharp.Compiler.PPLexer.tokenstream args
@@ -850,6 +850,8 @@ rule token (args: LexArgs) (skip: bool) = parse
| "..^" { DOT_DOT_HAT }
+ | "..." { DOT_DOT_DOT }
+
| "." { DOT }
| ":" { COLON }
@@ -1024,7 +1026,7 @@ rule token (args: LexArgs) (skip: bool) = parse
shouldStartLine args lexbuf m (FSComp.SR.lexHashIfMustBeFirst())
let lookup id = List.contains id args.conditionalDefines
let lexed = lexeme lexbuf
- let isTrue, expr = evalIfDefExpression lexbuf.StartPos lexbuf.ReportLibraryOnlyFeatures lexbuf.LanguageVersion lexbuf.StrictIndentation args lookup lexed
+ let isTrue, expr = evalIfDefExpression lexbuf.StartPos lexbuf.ReportLibraryOnlyFeatures lexbuf.LanguageVersion args lookup lexed
args.ifdefStack <- (IfDefIf,m) :: args.ifdefStack
IfdefStore.SaveIfHash(lexbuf, lexed, expr, m)
let contCase = if isTrue then LexerEndlineContinuation.Token else LexerEndlineContinuation.IfdefSkip(0, m)
@@ -1056,7 +1058,7 @@ rule token (args: LexArgs) (skip: bool) = parse
let lookup id = List.contains id args.conditionalDefines
// Result is discarded: in active code, a prior #if/#elif branch is executing,
// so this #elif always transitions to skipping. Eval is needed for trivia storage.
- let _, expr = evalIfDefExpression lexbuf.StartPos lexbuf.ReportLibraryOnlyFeatures lexbuf.LanguageVersion lexbuf.StrictIndentation args lookup lexed
+ let _, expr = evalIfDefExpression lexbuf.StartPos lexbuf.ReportLibraryOnlyFeatures lexbuf.LanguageVersion args lookup lexed
args.ifdefStack <- (IfDefElif,m) :: rest
IfdefStore.SaveElifHash(lexbuf, lexed, expr, m)
let tok = HASH_ELIF(m, lexed, LexCont.EndLine(args.ifdefStack, args.stringNest, LexerEndlineContinuation.IfdefSkip(0, m)))
@@ -1121,7 +1123,7 @@ and ifdefSkip (n: int) (m: range) (args: LexArgs) (skip: bool) = parse
else
let lexed = lexeme lexbuf
let lookup id = List.contains id args.conditionalDefines
- let _, expr = evalIfDefExpression lexbuf.StartPos lexbuf.ReportLibraryOnlyFeatures lexbuf.LanguageVersion lexbuf.StrictIndentation args lookup lexed
+ let _, expr = evalIfDefExpression lexbuf.StartPos lexbuf.ReportLibraryOnlyFeatures lexbuf.LanguageVersion args lookup lexed
IfdefStore.SaveIfHash(lexbuf, lexed, expr, m)
let tok = INACTIVECODE(LexCont.EndLine(args.ifdefStack, args.stringNest, LexerEndlineContinuation.IfdefSkip(n+1, m)))
if skip then endline (LexerEndlineContinuation.IfdefSkip(n+1, m)) args skip lexbuf else tok }
@@ -1160,7 +1162,7 @@ and ifdefSkip (n: int) (m: range) (args: LexArgs) (skip: bool) = parse
let evalAndSaveElif () =
let lookup id = List.contains id args.conditionalDefines
- let result, expr = evalIfDefExpression lexbuf.StartPos lexbuf.ReportLibraryOnlyFeatures lexbuf.LanguageVersion lexbuf.StrictIndentation args lookup lexed
+ let result, expr = evalIfDefExpression lexbuf.StartPos lexbuf.ReportLibraryOnlyFeatures lexbuf.LanguageVersion args lookup lexed
IfdefStore.SaveElifHash(lexbuf, lexed, expr, m)
result
diff --git a/src/Compiler/pars.fsy b/src/Compiler/pars.fsy
index 01120123a36..b83bcaefefd 100644
--- a/src/Compiler/pars.fsy
+++ b/src/Compiler/pars.fsy
@@ -80,7 +80,7 @@ let parse_error_rich = Some(fun (ctxt: ParseErrorContext<_>) ->
%token PERCENT_OP BINDER
%token LQUOTE RQUOTE RQUOTE_DOT RQUOTE_BAR_RBRACE
%token BAR_BAR UPCAST DOWNCAST NULL RESERVED MODULE NAMESPACE DELEGATE CONSTRAINT BASE
-%token AND AS ASSERT OASSERT ASR BEGIN DO DONE DOWNTO ELSE ELIF END DOT_DOT DOT_DOT_HAT
+%token AND AS ASSERT OASSERT ASR BEGIN DO DONE DOWNTO ELSE ELIF END DOT_DOT_DOT DOT_DOT DOT_DOT_HAT
%token EXCEPTION FALSE FOR FUN FUNCTION IF IN JOIN_IN FINALLY DO_BANG
%token LAZY OLAZY MATCH MATCH_BANG MUTABLE NEW OF
%token OPEN OR REC THEN TO TRUE TRY TYPE VAL INLINE INTERFACE INSTANCE CONST
@@ -1857,9 +1857,7 @@ classDefnMembersAtLeastOne:
| classDefnMember opt_seps classDefnMembers
{ match $1, $3 with
| [ SynMemberDefn.Interface(members=Some []; range=m) ], nextMember :: _ ->
- let strictIndentation = parseState.LexBuffer.SupportsFeature LanguageFeature.StrictIndentation
- let warnF = if strictIndentation then errorR else warning
- warnF(IndentationProblem(FSComp.SR.lexfltTokenIsOffsideOfContextStartedEarlier(warningStringOfPos m.Start), nextMember.Range))
+ errorR(IndentationProblem(FSComp.SR.lexfltTokenIsOffsideOfContextStartedEarlier(warningStringOfPos m.Start), nextMember.Range))
| _ -> ()
$1 @ $3 }
@@ -2163,7 +2161,6 @@ classDefnMember:
let leadingKeyword = SynTypeDefnLeadingKeyword.StaticType(rhs parseState 3, rhs parseState 4)
[ SynMemberDefn.NestedType($5 leadingKeyword, None, rhs2 parseState 1 5) ] }
-
/* A 'val' definition in an object type definition */
valDefnDecl:
| VAL opt_mutable opt_access ident COLON typ
@@ -2487,7 +2484,7 @@ tyconDefnOrSpfnSimpleRepr:
if parseState.LexBuffer.ReportLibraryOnlyFeatures then libraryOnlyError mLhs
if Option.isSome $2 then errorR(Error(FSComp.SR.parsInlineAssemblyCannotHaveVisibilityDeclarations(), rhs parseState 2))
let s, _ = $5
- let ilType = ParseAssemblyCodeType s parseState.LexBuffer.ReportLibraryOnlyFeatures parseState.LexBuffer.LanguageVersion parseState.LexBuffer.StrictIndentation (rhs parseState 5)
+ let ilType = ParseAssemblyCodeType s parseState.LexBuffer.ReportLibraryOnlyFeatures parseState.LexBuffer.LanguageVersion (rhs parseState 5)
SynTypeDefnSimpleRepr.LibraryOnlyILAssembly(box ilType, mLhs) }
@@ -2951,7 +2948,8 @@ unionCaseReprElement:
unionCaseRepr:
| braceFieldDeclList
{ errorR(Deprecated(FSComp.SR.parsConsiderUsingSeparateRecordType(), lhs parseState))
- $1, rhs parseState 1 }
+ let fields = $1 |> List.choose (function SynFieldOrSpread.Field field -> Some field | _ -> None)
+ fields, rhs parseState 1 }
| unionCaseReprElements
{ $1 }
@@ -2972,7 +2970,16 @@ recdFieldDecl:
let (SynField (a, b, c, d, e, xmlDoc, vis, mWhole, trivia)) = fld
if Option.isSome vis then errorR (Error (FSComp.SR.parsRecordFieldsCannotHaveVisibilityDeclarations (), rhs parseState 2))
let mWhole = unionRangeWithXmlDoc xmlDoc mWhole
- SynField (a, b, c, d, e, xmlDoc, None, mWhole, trivia) }
+ SynFieldOrSpread.Field (SynField (a, b, c, d, e, xmlDoc, None, mWhole, trivia)) }
+
+ | DOT_DOT_DOT typ
+ { let m = rhs2 parseState 1 2
+ SynFieldOrSpread.Spread (SynTypeSpread (rhs parseState 1, $2, m)) }
+
+ | DOT_DOT_DOT
+ { let m = rhs parseState 1
+ reportParseErrorAt m (FSComp.SR.parsMissingSpreadSrcTy ())
+ SynFieldOrSpread.Spread (SynTypeSpread (m, SynType.FromParseError m, m)) }
/* Part of a field or val declaration in a record type or object type */
fieldDecl:
@@ -4934,6 +4941,16 @@ declExpr:
{ let m = rhs parseState 1
SynExpr.IndexRange(None, m, None, m, m, m) }
+ | DOT_DOT_DOT declExpr
+ { let m = rhs parseState 1
+ reportParseErrorAt m (FSComp.SR.parsSpreadNotSupported ())
+ arbExpr ("dotDotDotDeclExpr", m) }
+
+ | DOT_DOT_DOT
+ { let m = rhs parseState 1
+ reportParseErrorAt m (FSComp.SR.parsSpreadNotSupported ())
+ arbExpr ("dotDotDot", m) }
+
| minusExpr %prec expr_prefix_plus_minus { $1 }
whileExprCore:
@@ -5656,6 +5673,11 @@ braceExpr:
{ let m, r = $2
r (rhs2 parseState 1 3) }
+ | LBRACE DOT_DOT_DOT rbrace
+ { let m = rhs parseState 2
+ reportParseErrorAt m (FSComp.SR.parsMissingSpreadSrcExpr ())
+ SynExpr.Record (None, None, rebindRanges (RecordBinding.Spread (SynExprSpread (m, arbExpr ("spreadSrcExpr", m), m))) [] None, m) }
+
| LBRACE braceExprBody recover
{ reportParseErrorAt (rhs parseState 1) (FSComp.SR.parsUnmatchedBrace())
let m, r = $2
@@ -5740,7 +5762,7 @@ inlineAssemblyExpr:
{ if parseState.LexBuffer.ReportLibraryOnlyFeatures then libraryOnlyWarning (lhs parseState)
let (s, _), sm = $2, rhs parseState 2
(fun m ->
- let ilInstrs = ParseAssemblyCodeInstructions s parseState.LexBuffer.ReportLibraryOnlyFeatures parseState.LexBuffer.LanguageVersion parseState.LexBuffer.StrictIndentation sm
+ let ilInstrs = ParseAssemblyCodeInstructions s parseState.LexBuffer.ReportLibraryOnlyFeatures parseState.LexBuffer.LanguageVersion sm
SynExpr.LibraryOnlyILAssembly(box ilInstrs, $3, List.rev $4, $5, m)) }
optCurriedArgExprs:
@@ -5779,8 +5801,11 @@ recdExpr:
{ let arg = match $4 with None -> mkSynUnit (lhs parseState) | Some e -> e
let l = List.rev $5
let dummyField = mkRecdField (SynLongIdent([], [], [])) // dummy identifier, it will be discarded
- let l = rebindRanges (dummyField, None, None) l $6
- let (SynExprRecordField(_, _, _, _, inheritsSep)) = List.head l
+ let l = rebindRanges (RecordBinding.Field (dummyField, None, None)) l $6
+ let inheritsSep =
+ match List.head l with
+ | SynExprRecordFieldOrSpread.Field (SynExprRecordField(_, _, _, _), inheritsSep) -> inheritsSep
+ | _ -> None
let bindings = List.tail l
(Some($2, arg, rhs2 parseState 2 4, inheritsSep, rhs parseState 1), None, bindings) }
@@ -5789,13 +5814,26 @@ recdExpr:
None, a, b }
recdExprCore:
+ | DOT_DOT_DOT declExprBlock recdExprBindings opt_seps_block
+ { let mSpread = rhs parseState 1
+ let m = rhs2 parseState 1 2
+ let l = List.rev $3
+ let l = rebindRanges (RecordBinding.Spread (SynExprSpread (mSpread, $2, m))) l $4
+ None, l }
+
+ | DOT_DOT_DOT
+ { let mSpread = rhs parseState 1
+ let m = mSpread
+ reportParseErrorAt m (FSComp.SR.parsMissingSpreadSrcExpr ())
+ None, rebindRanges (RecordBinding.Spread (SynExprSpread (mSpread, arbExpr ("spreadSrcExpr", m), m))) [] None }
+
| appExpr EQUALS declExprBlock recdExprBindings opt_seps_block
{ match $1 with
| LongOrSingleIdent(false, (SynLongIdent _ as f), None, m) ->
let f = mkRecdField f
let mEquals = rhs parseState 2
let l = List.rev $4
- let l = rebindRanges (f, Some mEquals, Some $3) l $5
+ let l = rebindRanges (RecordBinding.Field (f, Some mEquals, Some $3)) l $5
(None, l)
| _ -> raiseParseErrorAt (rhs parseState 2) (FSComp.SR.parsFieldBinding()) }
@@ -5804,7 +5842,7 @@ recdExprCore:
| LongOrSingleIdent(false, (SynLongIdent _ as f), None, m) ->
let f = mkRecdField f
let mEquals = rhs parseState 2
- let l = rebindRanges (f, Some mEquals, None) [] None
+ let l = rebindRanges (RecordBinding.Field (f, Some mEquals, None)) [] None
None, l
| _ -> raiseParseErrorAt (rhs parseState 2) (FSComp.SR.parsFieldBinding ()) }
@@ -5822,7 +5860,7 @@ recdExprCore:
reportParseErrorAt m (FSComp.SR.parsUnderscoreInvalidFieldName())
reportParseErrorAt m (FSComp.SR.parsFieldBinding())
let f = mkUnderscoreRecdField m
- (None, [ SynExprRecordField(f, None, None, m, None) ]) }
+ (None, [ SynExprRecordFieldOrSpread.Field (SynExprRecordField(f, None, None, m), None) ]) }
| UNDERSCORE EQUALS
{ let m = rhs parseState 1
@@ -5831,25 +5869,41 @@ recdExprCore:
let mEquals = rhs parseState 2
reportParseErrorAt (rhs2 parseState 1 2) (FSComp.SR.parsFieldBinding())
- (None, [ SynExprRecordField(f, Some mEquals, None, (rhs2 parseState 1 2), None) ]) }
+ (None, [ SynExprRecordFieldOrSpread.Field (SynExprRecordField(f, Some mEquals, None, (rhs2 parseState 1 2)), None) ]) }
| UNDERSCORE EQUALS declExprBlock recdExprBindings opt_seps_block
{ reportParseErrorAt (rhs parseState 1) (FSComp.SR.parsUnderscoreInvalidFieldName())
let f = mkUnderscoreRecdField (rhs parseState 1)
let mEquals = rhs parseState 2
let l = List.rev $4
- let l = rebindRanges (f, Some mEquals, Some $3) l $5
+ let l = rebindRanges (RecordBinding.Field (f, Some mEquals, Some $3)) l $5
(None, l) }
/* handles case like {x with} */
+ | DOT_DOT_DOT appExpr WITH recdBinding recdExprBindings opt_seps_block
+ { reportParseErrorAt (rhs parseState 1) (FSComp.SR.parsSpreadNotSupportedBeforeWith ())
+ let l = List.rev $5
+ let l = rebindRanges $4 l $6
+ (Some($2, (rhs parseState 3, None)), l) }
+
| appExpr WITH recdBinding recdExprBindings opt_seps_block
{ let l = List.rev $4
let l = rebindRanges $3 l $5
(Some($1, (rhs parseState 2, None)), l) }
+ | DOT_DOT_DOT appExpr OWITH opt_seps_block OEND
+ { reportParseErrorAt (rhs parseState 1) (FSComp.SR.parsSpreadNotSupportedBeforeWith ())
+ (Some($2, (rhs parseState 3, None)), []) }
+
| appExpr OWITH opt_seps_block OEND
{ (Some($1, (rhs parseState 2, None)), []) }
+ | DOT_DOT_DOT appExpr OWITH recdBinding recdExprBindings opt_seps_block OEND
+ { reportParseErrorAt (rhs parseState 1) (FSComp.SR.parsSpreadNotSupportedBeforeWith ())
+ let l = List.rev $5
+ let l = rebindRanges $4 l $6
+ (Some($2, (rhs parseState 3, None)), l) }
+
| appExpr OWITH recdBinding recdExprBindings opt_seps_block OEND
{ let l = List.rev $4
let l = rebindRanges $3 l $5
@@ -5895,27 +5949,38 @@ recdExprBindings:
{ [] }
recdBinding:
+ | DOT_DOT_DOT declExprBlock
+ { let mSpread = rhs parseState 1
+ let m = rhs2 parseState 1 2
+ RecordBinding.Spread (SynExprSpread (mSpread, $2, m)) }
+
| pathOrUnderscore EQUALS declExprBlock
{ let mEquals = rhs parseState 2
- ($1, Some mEquals, Some $3) }
+ RecordBinding.Field ($1, Some mEquals, Some $3) }
| pathOrUnderscore EQUALS
{ let mEquals = rhs parseState 2
reportParseErrorAt (rhs parseState 1) (FSComp.SR.parsFieldBinding())
- ($1, Some mEquals, None) }
+ RecordBinding.Field ($1, Some mEquals, None) }
| pathOrUnderscore EQUALS ends_coming_soon_or_recover
{ let mEquals = rhs parseState 2
reportParseErrorAt (rhs parseState 1) (FSComp.SR.parsFieldBinding())
- ($1, Some mEquals, None) }
+ RecordBinding.Field ($1, Some mEquals, None) }
| pathOrUnderscore
{ reportParseErrorAt (rhs parseState 1) (FSComp.SR.parsFieldBinding())
- ($1, None, None) }
+ RecordBinding.Field ($1, None, None) }
| pathOrUnderscore ends_coming_soon_or_recover
{ reportParseErrorAt (rhs parseState 1) (FSComp.SR.parsFieldBinding())
- ($1, None, None) }
+ RecordBinding.Field ($1, None, None) }
+
+ | DOT_DOT_DOT
+ { reportParseErrorAt (rhs parseState 1) (FSComp.SR.parsMissingSpreadSrcExpr ())
+ let mSpread = rhs parseState 1
+ let m = mSpread
+ RecordBinding.Spread (SynExprSpread (mSpread, arbExpr ("spreadSrcExpr", m), m)) }
/* There is a minor conflict between
seq { new ty() } // sequence expression with one very odd 'action' expression
@@ -6016,10 +6081,12 @@ braceBarExprCore:
{ let orig, flds = $2
let flds =
flds |> List.choose (function
- | SynExprRecordField((synLongIdent, _), mEquals, Some e, _, _) when orig.IsSome -> Some(synLongIdent, mEquals, e) // copy-and-update, long identifier signifies nesting
- | SynExprRecordField((SynLongIdent([ _id ], _, _) as synLongIdent, _), mEquals, Some e, _, _) -> Some(synLongIdent, mEquals, e) // record construction, long identifier not valid
- | SynExprRecordField((synLongIdent, _), mEquals, None, _, _) -> Some(synLongIdent, mEquals, arbExpr ("anonField", synLongIdent.Range))
- | _ -> reportParseErrorAt (rhs parseState 1) (FSComp.SR.parsInvalidAnonRecdType()); None)
+ | SynExprRecordFieldOrSpread.Field (SynExprRecordField((synLongIdent, _), mEquals, Some e, m), sep) ->
+ Some (SynExprAnonRecordFieldOrSpread.Field (SynExprAnonRecordField (synLongIdent, mEquals, e, m), sep)) // copy-and-update, long identifier signifies nesting
+ | SynExprRecordFieldOrSpread.Field (SynExprRecordField((synLongIdent, _), mEquals, None, m), sep) ->
+ Some (SynExprAnonRecordFieldOrSpread.Field (SynExprAnonRecordField (synLongIdent, mEquals, arbExpr ("anonField", synLongIdent.Range), m), sep))
+ | SynExprRecordFieldOrSpread.Spread (spread, sep) ->
+ Some (SynExprAnonRecordFieldOrSpread.Spread (spread, sep)))
let mLeftBrace = rhs parseState 1
let mRightBrace = rhs parseState 3
(fun (mStruct: range option) ->
@@ -6031,8 +6098,12 @@ braceBarExprCore:
let orig, flds = $2
let flds =
flds |> List.map (function
- | SynExprRecordField((synLongIdent, _), mEquals, Some e, _, _) -> (synLongIdent, mEquals, e)
- | SynExprRecordField((synLongIdent, _), mEquals, None, _, _) -> (synLongIdent, mEquals, arbExpr ("anonField", synLongIdent.Range)))
+ | SynExprRecordFieldOrSpread.Field (SynExprRecordField((synLongIdent, _), mEquals, Some e, m), sep) ->
+ SynExprAnonRecordFieldOrSpread.Field (SynExprAnonRecordField (synLongIdent, mEquals, e, m), sep)
+ | SynExprRecordFieldOrSpread.Field (SynExprRecordField((synLongIdent, _), mEquals, None, m), sep) ->
+ SynExprAnonRecordFieldOrSpread.Field (SynExprAnonRecordField (synLongIdent, mEquals, arbExpr ("anonField", synLongIdent.Range), m), sep)
+ | SynExprRecordFieldOrSpread.Spread (spread, sep) ->
+ SynExprAnonRecordFieldOrSpread.Spread (spread, sep))
let mLeftBrace = rhs parseState 1
let mExpr = rhs parseState 2
(fun (mStruct: range option) ->
@@ -6623,7 +6694,7 @@ atomTypeOrAnonRecdType:
{ let flds, isStruct = $1
let flds2 =
flds |> List.choose (function
- | (SynField([], false, Some id, ty, false, _xmldoc, None, _m, _trivia)) -> Some(id, ty)
+ | SynFieldOrSpread.Field (SynField([], false, Some id, ty, false, _xmldoc, None, _m, _trivia)) -> Some(id, ty)
| _ -> reportParseErrorAt (rhs parseState 1) (FSComp.SR.parsInvalidAnonRecdType()); None)
SynType.AnonRecd(isStruct, flds2, rhs parseState 1) }
@@ -7162,7 +7233,7 @@ interpolatedStringParts:
{ [ SynInterpolatedStringPart.String(fst $1, rhs parseState 1) ] }
| INTERP_STRING_PART interpolatedStringFill interpolatedStringParts
- { SynInterpolatedStringPart.String(fst $1, rhs parseState 1) :: SynInterpolatedStringPart.FillExpr $2 :: $3 }
+ { mkInterpolatedStringFillParts (fst $1, rhs parseState 1, $2) @ $3 }
| INTERP_STRING_PART interpolatedStringParts
{ let rbrace = parseState.InputEndPosition 1
@@ -7176,7 +7247,7 @@ interpolatedStringParts:
interpolatedString:
| INTERP_STRING_BEGIN_PART interpolatedStringFill interpolatedStringParts
{ let s, synStringKind, _ = $1
- SynInterpolatedStringPart.String(s, rhs parseState 1) :: SynInterpolatedStringPart.FillExpr $2 :: $3, synStringKind }
+ mkInterpolatedStringFillParts (s, rhs parseState 1, $2) @ $3, synStringKind }
| INTERP_STRING_BEGIN_END
{ let s, synStringKind, _ = $1
diff --git a/src/Compiler/xlf/FSComp.txt.cs.xlf b/src/Compiler/xlf/FSComp.txt.cs.xlf
index 48fae4742da..acda98d97e1 100644
--- a/src/Compiler/xlf/FSComp.txt.cs.xlf
+++ b/src/Compiler/xlf/FSComp.txt.cs.xlf
@@ -1,4 +1,4 @@
-
+
@@ -367,6 +367,11 @@
Deprecate places where 'seq' can be omitted
+
+ construct delegates that point directly at the target method, avoiding an intermediate closure
+ construct delegates that point directly at the target method, avoiding an intermediate closure
+
+ discard pattern in use bindingvzor discard ve vazbě použití
@@ -537,6 +542,11 @@
neproměnné vzory napravo od vzorů typu „jako“
+
+ honor the 'NotNullIfNotNull' attribute on a method's return value
+ honor the 'NotNullIfNotNull' attribute on a method's return value
+
+ nullable optional interopnepovinný zprostředkovatel komunikace s možnou hodnotou null
@@ -602,6 +612,11 @@
vypsat literály libovolné velikosti
+
+ record type and expression spreads
+ record type and expression spreads
+
+ informational messages related to reference cellsinformační zprávy související s referenčními buňkami
@@ -667,11 +682,6 @@
Statické členy v rozhraních
-
- Raises errors on incorrect indentation, allows better recovery and analysis during editing
- Vyvolává chyby při nesprávném odsazení, umožňuje lepší obnovení a analýzu během úprav
-
- string interpolationinterpolace řetězce
@@ -1117,11 +1127,6 @@
Zahrnout informace o rozhraní F#, výchozí je soubor. Klíčové pro distribuci knihoven.
-
- Override indentation rules implied by the language version ({0} by default)
- Override indentation rules implied by the language version ({0} by default)
-
- Supported language versions:Podporované jazykové verze:
@@ -1247,6 +1252,16 @@
Očekává se text člena
+
+ Missing spread source expression after '...'.
+ Missing spread source expression after '...'.
+
+
+
+ Missing spread source type after '...'.
+ Missing spread source type after '...'.
+
+ Missing union case nameChybí název případu sjednocení
@@ -1262,6 +1277,16 @@
V primárních konstruktorech jsou povoleny pouze jednoduché vzory.
+
+ Spreading is not supported in this construct.
+ Spreading is not supported in this construct.
+
+
+
+ Spreading is not supported in this position. Use one of the forms {{ ...expr1; A = expr2 }} or {{ expr1 with A = expr2 }} instead.
+ Spreading is not supported in this position. Use one of the forms {{ ...expr1; A = expr2 }} or {{ expr1 with A = expr2 }} instead.
+
+ Incomplete declaration of a static construct. Use 'static let','static do','static member' or 'static val' for declaration.Neúplná deklarace statického konstruktoru. Pro deklaraci použijte „static let“, „static do“, „static member“ nebo „static val“.
@@ -1482,6 +1507,16 @@
Pole {0} se v tomto anonymním typu záznamu vyskytuje vícekrát.
+
+ The source expression of a spread into an anonymous record expression cannot be nullable.
+ The source expression of a spread into an anonymous record expression cannot be nullable.
+
+
+
+ The source expression of a spread into an anonymous record expression must have a nominal or anonymous record type.
+ The source expression of a spread into an anonymous record expression must have a nominal or anonymous record type.
+
+ This attribute is not valid for use on union cases with fields.This attribute is not valid for use on union cases with fields.
@@ -1527,6 +1562,11 @@
Expected unit-of-measure type parameter must be marked with the [<Measure>] attribute.
+
+ Generic attribute types are not supported in F#. The type '{0}' has type parameters and cannot be used as an attribute.
+ Generic attribute types are not supported in F#. The type '{0}' has type parameters and cannot be used as an attribute.
+
+ The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'.Syntaxe expr1[expr2] se používá pro indexování. Pokud chcete povolit indexování, zvažte možnost přidat anotaci typu, nebo pokud voláte funkci, přidejte mezeru, třeba expr1 [expr2].
@@ -1767,6 +1807,41 @@
The value or member '{0}' has been marked 'inline' but is part of a recursive binding group. F# does not support recursive 'inline' values. Either remove the 'inline' modifier or refactor the recursion.
+
+ Spread field '{0}' shadows an explicitly declared field with the same name.
+ Spread field '{0}' shadows an explicitly declared field with the same name.
+
+
+
+ The source expression of a spread into a nominal record expression cannot be nullable.
+ The source expression of a spread into a nominal record expression cannot be nullable.
+
+
+
+ The source expression of a spread into a nominal record expression must have a nominal or anonymous record type.
+ The source expression of a spread into a nominal record expression must have a nominal or anonymous record type.
+
+
+
+ Spread expressions and 'with' cannot be used together in the same copy-and-update expression.
+ Spread expressions and 'with' cannot be used together in the same copy-and-update expression.
+
+
+
+ Spread field '{0}' from type '{1}' shadows an explicitly declared field with the same name.
+ Spread field '{0}' from type '{1}' shadows an explicitly declared field with the same name.
+
+
+
+ The source type of a spread into a record type definition cannot be nullable.
+ The source type of a spread into a record type definition cannot be nullable.
+
+
+
+ The source type of a spread into a record type definition must itself be a nominal or anonymous record type.
+ The source type of a spread into a record type definition must itself be a nominal or anonymous record type.
+
+ The 'let! ... and! ...' construct may only be used if the computation expression builder defines either a '{0}' method or appropriate 'MergeSources' and 'Bind' methodsKonstrukt „let! ... and! ...“ se dá použít jen v případě, že tvůrce výpočetních výrazů definuje buď metodu „{0}“, nebo vhodné metody „MergeSource“ a „Bind“.
@@ -1852,6 +1927,11 @@
Vlastnost nesmí určovat volitelné argumenty, in, out, ParamArray, CallerInfo nebo Quote.
+
+ This type definition involves a cyclic reference through a spread.
+ This type definition involves a cyclic reference through a spread.
+
+ The type '{0}' does not support a nullness qualification.The type '{0}' does not support a nullness qualification.
@@ -6628,8 +6708,8 @@
- Unexpected syntax or possible incorrect indentation: this token is offside of context started at position {0}. Try indenting this further.\nTo continue using non-conforming indentation, pass the '--strict-indentation-' flag to the compiler, or set the language version to F# 7.
- Neočekávaná syntaxe nebo možné nesprávné odsazení: Tento token je mimo kontext spuštěný na pozici {0}. Zkuste toto odsazení ještě více odsadit.\nPokud chcete dál používat neodpovídající odsazení, předejte kompilátoru příznak '--strict-indentation-' nebo nastavte jazykovou verzi na F# 7.
+ Unexpected syntax or possible incorrect indentation: this token is offside of context started at position {0}. Try indenting this further.
+ Neočekávaná syntaxe nebo možné nesprávné odsazení: Tento token je mimo kontext spuštěný na pozici {0}. Zkuste toto odsazení ještě více odsadit.\nPokud chcete dál používat neodpovídající odsazení, předejte kompilátoru příznak '--strict-indentation-' nebo nastavte jazykovou verzi na F# 7.
diff --git a/src/Compiler/xlf/FSComp.txt.de.xlf b/src/Compiler/xlf/FSComp.txt.de.xlf
index 256a5b49e0f..eaa6f820a95 100644
--- a/src/Compiler/xlf/FSComp.txt.de.xlf
+++ b/src/Compiler/xlf/FSComp.txt.de.xlf
@@ -1,4 +1,4 @@
-
+
@@ -367,6 +367,11 @@
Deprecate places where 'seq' can be omitted
+
+ construct delegates that point directly at the target method, avoiding an intermediate closure
+ construct delegates that point directly at the target method, avoiding an intermediate closure
+
+ discard pattern in use bindingDas Verwerfen des verwendeten Musters ist verbindlich.
@@ -537,6 +542,11 @@
Nicht-Variablenmuster rechts neben as-Mustern
+
+ honor the 'NotNullIfNotNull' attribute on a method's return value
+ honor the 'NotNullIfNotNull' attribute on a method's return value
+
+ nullable optional interopInterop, NULL-Werte zulassend, optional
@@ -602,6 +612,11 @@
Literale beliebiger Größe auflisten
+
+ record type and expression spreads
+ record type and expression spreads
+
+ informational messages related to reference cellsInformationsmeldungen im Zusammenhang mit Bezugszellen
@@ -667,11 +682,6 @@
Statische Member in Schnittstellen
-
- Raises errors on incorrect indentation, allows better recovery and analysis during editing
- Löst Fehler bei fehlerhaftem Einzug aus und ermöglicht eine bessere Wiederherstellung und Analyse während der Bearbeitung.
-
- string interpolationZeichenfolgeninterpolation
@@ -1117,11 +1127,6 @@
Schließen Sie F#-Schnittstelleninformationen ein, der Standardwert ist „file“. Wesentlich für die Verteilung von Bibliotheken.
-
- Override indentation rules implied by the language version ({0} by default)
- Override indentation rules implied by the language version ({0} by default)
-
- Supported language versions:Unterstützte Sprachversionen:
@@ -1247,6 +1252,16 @@
Membertext wird erwartet
+
+ Missing spread source expression after '...'.
+ Missing spread source expression after '...'.
+
+
+
+ Missing spread source type after '...'.
+ Missing spread source type after '...'.
+
+ Missing union case nameFehlender Union-Fallname
@@ -1262,6 +1277,16 @@
In primären Konstruktoren sind nur einfache Muster zulässig
+
+ Spreading is not supported in this construct.
+ Spreading is not supported in this construct.
+
+
+
+ Spreading is not supported in this position. Use one of the forms {{ ...expr1; A = expr2 }} or {{ expr1 with A = expr2 }} instead.
+ Spreading is not supported in this position. Use one of the forms {{ ...expr1; A = expr2 }} or {{ expr1 with A = expr2 }} instead.
+
+ Incomplete declaration of a static construct. Use 'static let','static do','static member' or 'static val' for declaration.Unvollständige Deklaration eines statischen Konstrukts. Verwenden Sie "static let", "static do", "static member" oder "static val" für die Deklaration.
@@ -1482,6 +1507,16 @@
Das Feld "{0}" ist in diesem anonymen Datensatztyp mehrmals vorhanden.
+
+ The source expression of a spread into an anonymous record expression cannot be nullable.
+ The source expression of a spread into an anonymous record expression cannot be nullable.
+
+
+
+ The source expression of a spread into an anonymous record expression must have a nominal or anonymous record type.
+ The source expression of a spread into an anonymous record expression must have a nominal or anonymous record type.
+
+ This attribute is not valid for use on union cases with fields.This attribute is not valid for use on union cases with fields.
@@ -1527,6 +1562,11 @@
Expected unit-of-measure type parameter must be marked with the [<Measure>] attribute.
+
+ Generic attribute types are not supported in F#. The type '{0}' has type parameters and cannot be used as an attribute.
+ Generic attribute types are not supported in F#. The type '{0}' has type parameters and cannot be used as an attribute.
+
+ The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'.Die Syntax "expr1[expr2]" wird für die Indizierung verwendet. Fügen Sie ggf. eine Typanmerkung hinzu, um die Indizierung zu aktivieren, oder fügen Sie beim Aufrufen einer Funktion ein Leerzeichen hinzu, z. B. "expr1 [expr2]".
@@ -1767,6 +1807,41 @@
The value or member '{0}' has been marked 'inline' but is part of a recursive binding group. F# does not support recursive 'inline' values. Either remove the 'inline' modifier or refactor the recursion.
+
+ Spread field '{0}' shadows an explicitly declared field with the same name.
+ Spread field '{0}' shadows an explicitly declared field with the same name.
+
+
+
+ The source expression of a spread into a nominal record expression cannot be nullable.
+ The source expression of a spread into a nominal record expression cannot be nullable.
+
+
+
+ The source expression of a spread into a nominal record expression must have a nominal or anonymous record type.
+ The source expression of a spread into a nominal record expression must have a nominal or anonymous record type.
+
+
+
+ Spread expressions and 'with' cannot be used together in the same copy-and-update expression.
+ Spread expressions and 'with' cannot be used together in the same copy-and-update expression.
+
+
+
+ Spread field '{0}' from type '{1}' shadows an explicitly declared field with the same name.
+ Spread field '{0}' from type '{1}' shadows an explicitly declared field with the same name.
+
+
+
+ The source type of a spread into a record type definition cannot be nullable.
+ The source type of a spread into a record type definition cannot be nullable.
+
+
+
+ The source type of a spread into a record type definition must itself be a nominal or anonymous record type.
+ The source type of a spread into a record type definition must itself be a nominal or anonymous record type.
+
+ The 'let! ... and! ...' construct may only be used if the computation expression builder defines either a '{0}' method or appropriate 'MergeSources' and 'Bind' methodsDas Konstrukt "let! ... and! ..." kann nur verwendet werden, wenn der Berechnungsausdrucks-Generator entweder eine {0}-Methode oder geeignete MergeSources- und Bind-Methoden definiert.
@@ -1852,6 +1927,11 @@
Ein Merkmal darf keine Argumente für „optional“, „in“, „out“, „ParamArray“", „CallerInfo“ oder „Quote“ angeben.
+
+ This type definition involves a cyclic reference through a spread.
+ This type definition involves a cyclic reference through a spread.
+
+ The type '{0}' does not support a nullness qualification.The type '{0}' does not support a nullness qualification.
@@ -6628,8 +6708,8 @@
- Unexpected syntax or possible incorrect indentation: this token is offside of context started at position {0}. Try indenting this further.\nTo continue using non-conforming indentation, pass the '--strict-indentation-' flag to the compiler, or set the language version to F# 7.
- Unerwartete Syntax oder möglicherweise falscher Einzug: Dieses Token befindet sich außerhalb des Kontexts, der an Position {0}gestartet wurde. Versuchen Sie, dies weiter einzurücken.\nUm weiterhin eine nicht konforme Einrückung zu verwenden, übergeben Sie dem Compiler das Flag „--strict-indentation-“ oder setzen Sie die Sprachversion auf F# 7.
+ Unexpected syntax or possible incorrect indentation: this token is offside of context started at position {0}. Try indenting this further.
+ Unerwartete Syntax oder möglicherweise falscher Einzug: Dieses Token befindet sich außerhalb des Kontexts, der an Position {0}gestartet wurde. Versuchen Sie, dies weiter einzurücken.\nUm weiterhin eine nicht konforme Einrückung zu verwenden, übergeben Sie dem Compiler das Flag „--strict-indentation-“ oder setzen Sie die Sprachversion auf F# 7.
diff --git a/src/Compiler/xlf/FSComp.txt.es.xlf b/src/Compiler/xlf/FSComp.txt.es.xlf
index 965b77b54c1..b6f9e45d7dd 100644
--- a/src/Compiler/xlf/FSComp.txt.es.xlf
+++ b/src/Compiler/xlf/FSComp.txt.es.xlf
@@ -1,4 +1,4 @@
-
+
@@ -367,6 +367,11 @@
Deprecate places where 'seq' can be omitted
+
+ construct delegates that point directly at the target method, avoiding an intermediate closure
+ construct delegates that point directly at the target method, avoiding an intermediate closure
+
+ discard pattern in use bindingdescartar enlace de patrón en uso
@@ -537,6 +542,11 @@
patrones no variables a la derecha de los patrones "as"
+
+ honor the 'NotNullIfNotNull' attribute on a method's return value
+ honor the 'NotNullIfNotNull' attribute on a method's return value
+
+ nullable optional interopinteroperabilidad opcional que admite valores NULL
@@ -602,6 +612,11 @@
enumerar literales de cualquier tamaño
+
+ record type and expression spreads
+ record type and expression spreads
+
+ informational messages related to reference cellsmensajes informativos relacionados con las celdas de referencia
@@ -667,11 +682,6 @@
Miembros estáticos en interfaces
-
- Raises errors on incorrect indentation, allows better recovery and analysis during editing
- Genera errores en una sangría incorrecta, permite una mejor recuperación y análisis durante la edición.
-
- string interpolationinterpolación de cadena
@@ -1117,11 +1127,6 @@
Incluir información de interfaz de F#, el valor predeterminado es file. Esencial para distribuir bibliotecas.
-
- Override indentation rules implied by the language version ({0} by default)
- Override indentation rules implied by the language version ({0} by default)
-
- Supported language versions:Versiones de lenguaje admitidas:
@@ -1247,6 +1252,16 @@
Se espera el cuerpo del miembro
+
+ Missing spread source expression after '...'.
+ Missing spread source expression after '...'.
+
+
+
+ Missing spread source type after '...'.
+ Missing spread source type after '...'.
+
+ Missing union case nameFalta el nombre del caso de unión
@@ -1262,6 +1277,16 @@
Solo se permiten patrones simples en constructores principales
+
+ Spreading is not supported in this construct.
+ Spreading is not supported in this construct.
+
+
+
+ Spreading is not supported in this position. Use one of the forms {{ ...expr1; A = expr2 }} or {{ expr1 with A = expr2 }} instead.
+ Spreading is not supported in this position. Use one of the forms {{ ...expr1; A = expr2 }} or {{ expr1 with A = expr2 }} instead.
+
+ Incomplete declaration of a static construct. Use 'static let','static do','static member' or 'static val' for declaration.Declaración incompleta de una construcción estática. Use "static let", "static do", "static member" o "static val" para la declaración.
@@ -1482,6 +1507,16 @@
El campo "{0}" aparece varias veces en este tipo de registro anónimo.
+
+ The source expression of a spread into an anonymous record expression cannot be nullable.
+ The source expression of a spread into an anonymous record expression cannot be nullable.
+
+
+
+ The source expression of a spread into an anonymous record expression must have a nominal or anonymous record type.
+ The source expression of a spread into an anonymous record expression must have a nominal or anonymous record type.
+
+ This attribute is not valid for use on union cases with fields.This attribute is not valid for use on union cases with fields.
@@ -1527,6 +1562,11 @@
Expected unit-of-measure type parameter must be marked with the [<Measure>] attribute.
+
+ Generic attribute types are not supported in F#. The type '{0}' has type parameters and cannot be used as an attribute.
+ Generic attribute types are not supported in F#. The type '{0}' has type parameters and cannot be used as an attribute.
+
+ The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'.La sintaxis "expr1[expr2]" se usa para la indexación. Considere la posibilidad de agregar una anotación de tipo para habilitar la indexación, si se llama a una función, agregue un espacio, por ejemplo, "expr1 [expr2]".
@@ -1767,6 +1807,41 @@
The value or member '{0}' has been marked 'inline' but is part of a recursive binding group. F# does not support recursive 'inline' values. Either remove the 'inline' modifier or refactor the recursion.
+
+ Spread field '{0}' shadows an explicitly declared field with the same name.
+ Spread field '{0}' shadows an explicitly declared field with the same name.
+
+
+
+ The source expression of a spread into a nominal record expression cannot be nullable.
+ The source expression of a spread into a nominal record expression cannot be nullable.
+
+
+
+ The source expression of a spread into a nominal record expression must have a nominal or anonymous record type.
+ The source expression of a spread into a nominal record expression must have a nominal or anonymous record type.
+
+
+
+ Spread expressions and 'with' cannot be used together in the same copy-and-update expression.
+ Spread expressions and 'with' cannot be used together in the same copy-and-update expression.
+
+
+
+ Spread field '{0}' from type '{1}' shadows an explicitly declared field with the same name.
+ Spread field '{0}' from type '{1}' shadows an explicitly declared field with the same name.
+
+
+
+ The source type of a spread into a record type definition cannot be nullable.
+ The source type of a spread into a record type definition cannot be nullable.
+
+
+
+ The source type of a spread into a record type definition must itself be a nominal or anonymous record type.
+ The source type of a spread into a record type definition must itself be a nominal or anonymous record type.
+
+ The 'let! ... and! ...' construct may only be used if the computation expression builder defines either a '{0}' method or appropriate 'MergeSources' and 'Bind' methodsLa construcción "let! ... and! ..." solo se puede usar si el generador de expresiones de cálculo define un método "{0}" o bien los métodos "MergeSources" y "Bind" adecuados.
@@ -1852,6 +1927,11 @@
Un rasgo no puede especificar argumentos opcionales, in, out, ParamArray, CallerInfo o Quote
+
+ This type definition involves a cyclic reference through a spread.
+ This type definition involves a cyclic reference through a spread.
+
+ The type '{0}' does not support a nullness qualification.The type '{0}' does not support a nullness qualification.
@@ -6628,8 +6708,8 @@
- Unexpected syntax or possible incorrect indentation: this token is offside of context started at position {0}. Try indenting this further.\nTo continue using non-conforming indentation, pass the '--strict-indentation-' flag to the compiler, or set the language version to F# 7.
- Sintaxis inesperada o posible sangría incorrecta: este token está fuera del contexto iniciado en la posición {0}. Intente aplicar más sangría.\nPara seguir usando la sangría no conforme, pase la marca "--strict-indentation-" al compilador o establezca la versión del lenguaje en F# 7.
+ Unexpected syntax or possible incorrect indentation: this token is offside of context started at position {0}. Try indenting this further.
+ Sintaxis inesperada o posible sangría incorrecta: este token está fuera del contexto iniciado en la posición {0}. Intente aplicar más sangría.\nPara seguir usando la sangría no conforme, pase la marca "--strict-indentation-" al compilador o establezca la versión del lenguaje en F# 7.
diff --git a/src/Compiler/xlf/FSComp.txt.fr.xlf b/src/Compiler/xlf/FSComp.txt.fr.xlf
index 36af4f462ea..590ea0015b4 100644
--- a/src/Compiler/xlf/FSComp.txt.fr.xlf
+++ b/src/Compiler/xlf/FSComp.txt.fr.xlf
@@ -1,4 +1,4 @@
-
+
@@ -367,6 +367,11 @@
Deprecate places where 'seq' can be omitted
+
+ construct delegates that point directly at the target method, avoiding an intermediate closure
+ construct delegates that point directly at the target method, avoiding an intermediate closure
+
+ discard pattern in use bindingannuler le modèle dans la liaison d’utilisation
@@ -537,6 +542,11 @@
modèles non variables à droite de modèles « as »
+
+ honor the 'NotNullIfNotNull' attribute on a method's return value
+ honor the 'NotNullIfNotNull' attribute on a method's return value
+
+ nullable optional interopinteropérabilité facultative pouvant accepter une valeur null
@@ -602,6 +612,11 @@
répertorier les littéraux de n’importe quelle taille
+
+ record type and expression spreads
+ record type and expression spreads
+
+ informational messages related to reference cellsmessages d’information liés aux cellules de référence
@@ -667,11 +682,6 @@
Membres statiques dans les interfaces
-
- Raises errors on incorrect indentation, allows better recovery and analysis during editing
- Génère des erreurs en cas d'indentation incorrecte, permet une meilleure récupération et analyse lors de l'édition
-
- string interpolationinterpolation de chaîne
@@ -1117,11 +1127,6 @@
Incluez les informations de l’interface F#, la valeur par défaut est un fichier. Essentiel pour la distribution des bibliothèques.
-
- Override indentation rules implied by the language version ({0} by default)
- Override indentation rules implied by the language version ({0} by default)
-
- Supported language versions:Versions linguistiques prises en charge :
@@ -1247,6 +1252,16 @@
Comité membre attendu
+
+ Missing spread source expression after '...'.
+ Missing spread source expression after '...'.
+
+
+
+ Missing spread source type after '...'.
+ Missing spread source type after '...'.
+
+ Missing union case nameNom du cas syndical manquant
@@ -1262,6 +1277,16 @@
Seuls les modèles simples sont autorisés dans les constructeurs principaux
+
+ Spreading is not supported in this construct.
+ Spreading is not supported in this construct.
+
+
+
+ Spreading is not supported in this position. Use one of the forms {{ ...expr1; A = expr2 }} or {{ expr1 with A = expr2 }} instead.
+ Spreading is not supported in this position. Use one of the forms {{ ...expr1; A = expr2 }} or {{ expr1 with A = expr2 }} instead.
+
+ Incomplete declaration of a static construct. Use 'static let','static do','static member' or 'static val' for declaration.Déclaration incomplète d’une construction statique. Utilisez « static let », « static do », « static member » ou « static val » pour la déclaration.
@@ -1482,6 +1507,16 @@
Le champ '{0}' apparaît plusieurs fois dans ce type d'enregistrement anonyme.
+
+ The source expression of a spread into an anonymous record expression cannot be nullable.
+ The source expression of a spread into an anonymous record expression cannot be nullable.
+
+
+
+ The source expression of a spread into an anonymous record expression must have a nominal or anonymous record type.
+ The source expression of a spread into an anonymous record expression must have a nominal or anonymous record type.
+
+ This attribute is not valid for use on union cases with fields.This attribute is not valid for use on union cases with fields.
@@ -1527,6 +1562,11 @@
Expected unit-of-measure type parameter must be marked with the [<Measure>] attribute.
+
+ Generic attribute types are not supported in F#. The type '{0}' has type parameters and cannot be used as an attribute.
+ Generic attribute types are not supported in F#. The type '{0}' has type parameters and cannot be used as an attribute.
+
+ The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'.La syntaxe « expr1[expr2] » est utilisée pour l’indexation. Envisagez d’ajouter une annotation de type pour activer l’indexation, ou si vous appelez une fonction, ajoutez un espace, par exemple « expr1 [expr2] ».
@@ -1767,6 +1807,41 @@
The value or member '{0}' has been marked 'inline' but is part of a recursive binding group. F# does not support recursive 'inline' values. Either remove the 'inline' modifier or refactor the recursion.
+
+ Spread field '{0}' shadows an explicitly declared field with the same name.
+ Spread field '{0}' shadows an explicitly declared field with the same name.
+
+
+
+ The source expression of a spread into a nominal record expression cannot be nullable.
+ The source expression of a spread into a nominal record expression cannot be nullable.
+
+
+
+ The source expression of a spread into a nominal record expression must have a nominal or anonymous record type.
+ The source expression of a spread into a nominal record expression must have a nominal or anonymous record type.
+
+
+
+ Spread expressions and 'with' cannot be used together in the same copy-and-update expression.
+ Spread expressions and 'with' cannot be used together in the same copy-and-update expression.
+
+
+
+ Spread field '{0}' from type '{1}' shadows an explicitly declared field with the same name.
+ Spread field '{0}' from type '{1}' shadows an explicitly declared field with the same name.
+
+
+
+ The source type of a spread into a record type definition cannot be nullable.
+ The source type of a spread into a record type definition cannot be nullable.
+
+
+
+ The source type of a spread into a record type definition must itself be a nominal or anonymous record type.
+ The source type of a spread into a record type definition must itself be a nominal or anonymous record type.
+
+ The 'let! ... and! ...' construct may only be used if the computation expression builder defines either a '{0}' method or appropriate 'MergeSources' and 'Bind' methodsLe « laissez ! » ... et! ...' ne peut être utilisée que si le générateur d'expression de calcul définit soit une méthode '{0}', soit des méthodes 'MergeSources' et 'Bind' appropriées.
@@ -1852,6 +1927,11 @@
Une caractéristique ne peut pas spécifier d’arguments facultatifs, in, out, ParamArray, CallerInfo ou Quote
+
+ This type definition involves a cyclic reference through a spread.
+ This type definition involves a cyclic reference through a spread.
+
+ The type '{0}' does not support a nullness qualification.The type '{0}' does not support a nullness qualification.
@@ -6628,8 +6708,8 @@
- Unexpected syntax or possible incorrect indentation: this token is offside of context started at position {0}. Try indenting this further.\nTo continue using non-conforming indentation, pass the '--strict-indentation-' flag to the compiler, or set the language version to F# 7.
- Syntaxe inattendue ou mise en retrait incorrecte possible : ce jeton est hors du contexte démarré à la position {0}. Essayez de mettre cela en retrait.\nPour continuer à utiliser une mise en retrait non conforme, passez l’indicateur '--strict-indentation-' au compilateur ou définissez la version de langage sur F# 7.
+ Unexpected syntax or possible incorrect indentation: this token is offside of context started at position {0}. Try indenting this further.
+ Syntaxe inattendue ou mise en retrait incorrecte possible : ce jeton est hors du contexte démarré à la position {0}. Essayez de mettre cela en retrait.\nPour continuer à utiliser une mise en retrait non conforme, passez l’indicateur '--strict-indentation-' au compilateur ou définissez la version de langage sur F# 7.
diff --git a/src/Compiler/xlf/FSComp.txt.it.xlf b/src/Compiler/xlf/FSComp.txt.it.xlf
index cf5834247b2..6ef40f0aae4 100644
--- a/src/Compiler/xlf/FSComp.txt.it.xlf
+++ b/src/Compiler/xlf/FSComp.txt.it.xlf
@@ -1,4 +1,4 @@
-
+
@@ -367,6 +367,11 @@
Deprecate places where 'seq' can be omitted
+
+ construct delegates that point directly at the target method, avoiding an intermediate closure
+ construct delegates that point directly at the target method, avoiding an intermediate closure
+
+ discard pattern in use bindingrimuovi criterio nell'utilizzo dell'associazione
@@ -537,6 +542,11 @@
modelli non variabili a destra dei modelli 'as'
+
+ honor the 'NotNullIfNotNull' attribute on a method's return value
+ honor the 'NotNullIfNotNull' attribute on a method's return value
+
+ nullable optional interopInterop facoltativo nullable
@@ -602,6 +612,11 @@
elenca valori letterali di qualsiasi dimensione
+
+ record type and expression spreads
+ record type and expression spreads
+
+ informational messages related to reference cellsmessaggi informativi relativi alle celle di riferimento
@@ -667,11 +682,6 @@
Membri statici nelle interfacce
-
- Raises errors on incorrect indentation, allows better recovery and analysis during editing
- Genera errori di rientro non corretto. Consente un ripristino e un'analisi migliori durante la modifica
-
- string interpolationinterpolazione di stringhe
@@ -1117,11 +1127,6 @@
Includere le informazioni sull'interfaccia F#. Il valore predefinito è file. Essential per la distribuzione di librerie.
-
- Override indentation rules implied by the language version ({0} by default)
- Override indentation rules implied by the language version ({0} by default)
-
- Supported language versions:Versioni del linguaggio supportate:
@@ -1247,6 +1252,16 @@
Previsto corpo del membro
+
+ Missing spread source expression after '...'.
+ Missing spread source expression after '...'.
+
+
+
+ Missing spread source type after '...'.
+ Missing spread source type after '...'.
+
+ Missing union case nameNome case di unione mancante
@@ -1262,6 +1277,16 @@
Nei costruttori primari sono consentiti solo criteri semplici
+
+ Spreading is not supported in this construct.
+ Spreading is not supported in this construct.
+
+
+
+ Spreading is not supported in this position. Use one of the forms {{ ...expr1; A = expr2 }} or {{ expr1 with A = expr2 }} instead.
+ Spreading is not supported in this position. Use one of the forms {{ ...expr1; A = expr2 }} or {{ expr1 with A = expr2 }} instead.
+
+ Incomplete declaration of a static construct. Use 'static let','static do','static member' or 'static val' for declaration.Dichiarazione incompleta di un costrutto statico. Usare 'static let','static do','static member' o 'static val' per la dichiarazione.
@@ -1482,6 +1507,16 @@
Il campo '{0}' viene visualizzato più volte in questo tipo di record anonimo.
+
+ The source expression of a spread into an anonymous record expression cannot be nullable.
+ The source expression of a spread into an anonymous record expression cannot be nullable.
+
+
+
+ The source expression of a spread into an anonymous record expression must have a nominal or anonymous record type.
+ The source expression of a spread into an anonymous record expression must have a nominal or anonymous record type.
+
+ This attribute is not valid for use on union cases with fields.This attribute is not valid for use on union cases with fields.
@@ -1527,6 +1562,11 @@
Expected unit-of-measure type parameter must be marked with the [<Measure>] attribute.
+
+ Generic attribute types are not supported in F#. The type '{0}' has type parameters and cannot be used as an attribute.
+ Generic attribute types are not supported in F#. The type '{0}' has type parameters and cannot be used as an attribute.
+
+ The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'.La sintassi 'expr1[expr2]' viene usata per l'indicizzazione. Provare ad aggiungere un'annotazione di tipo per abilitare l'indicizzazione oppure se la chiamata a una funzione aggiunge uno spazio, ad esempio 'expr1 [expr2]'.
@@ -1767,6 +1807,41 @@
The value or member '{0}' has been marked 'inline' but is part of a recursive binding group. F# does not support recursive 'inline' values. Either remove the 'inline' modifier or refactor the recursion.
+
+ Spread field '{0}' shadows an explicitly declared field with the same name.
+ Spread field '{0}' shadows an explicitly declared field with the same name.
+
+
+
+ The source expression of a spread into a nominal record expression cannot be nullable.
+ The source expression of a spread into a nominal record expression cannot be nullable.
+
+
+
+ The source expression of a spread into a nominal record expression must have a nominal or anonymous record type.
+ The source expression of a spread into a nominal record expression must have a nominal or anonymous record type.
+
+
+
+ Spread expressions and 'with' cannot be used together in the same copy-and-update expression.
+ Spread expressions and 'with' cannot be used together in the same copy-and-update expression.
+
+
+
+ Spread field '{0}' from type '{1}' shadows an explicitly declared field with the same name.
+ Spread field '{0}' from type '{1}' shadows an explicitly declared field with the same name.
+
+
+
+ The source type of a spread into a record type definition cannot be nullable.
+ The source type of a spread into a record type definition cannot be nullable.
+
+
+
+ The source type of a spread into a record type definition must itself be a nominal or anonymous record type.
+ The source type of a spread into a record type definition must itself be a nominal or anonymous record type.
+
+ The 'let! ... and! ...' construct may only be used if the computation expression builder defines either a '{0}' method or appropriate 'MergeSources' and 'Bind' methodsÈ possibile usare il costrutto "let! ... and! ..." solo se il generatore di espressioni di calcolo definisce un metodo "{0}" o metodi "MergeSource" e "Bind" appropriati
@@ -1852,6 +1927,11 @@
Un tratto non può specificare argomenti optional, in, out, ParamArray, CallerInfo o Quote
+
+ This type definition involves a cyclic reference through a spread.
+ This type definition involves a cyclic reference through a spread.
+
+ The type '{0}' does not support a nullness qualification.The type '{0}' does not support a nullness qualification.
@@ -6628,8 +6708,8 @@
- Unexpected syntax or possible incorrect indentation: this token is offside of context started at position {0}. Try indenting this further.\nTo continue using non-conforming indentation, pass the '--strict-indentation-' flag to the compiler, or set the language version to F# 7.
- Sintassi imprevista o possibile rientro non corretto: questo token è fuori dal contesto avviato nella posizione {0}. Provare a impostare ulteriormente il rientro.\nPer continuare a usare un rientro non conforme, passare il flag '--strict-indentation-' al compilatore, o impostare la versione del linguaggio su F# 7.
+ Unexpected syntax or possible incorrect indentation: this token is offside of context started at position {0}. Try indenting this further.
+ Sintassi imprevista o possibile rientro non corretto: questo token è fuori dal contesto avviato nella posizione {0}. Provare a impostare ulteriormente il rientro.\nPer continuare a usare un rientro non conforme, passare il flag '--strict-indentation-' al compilatore, o impostare la versione del linguaggio su F# 7.
diff --git a/src/Compiler/xlf/FSComp.txt.ja.xlf b/src/Compiler/xlf/FSComp.txt.ja.xlf
index d684f435a7f..883e3285d63 100644
--- a/src/Compiler/xlf/FSComp.txt.ja.xlf
+++ b/src/Compiler/xlf/FSComp.txt.ja.xlf
@@ -1,4 +1,4 @@
-
+
@@ -367,6 +367,11 @@
Deprecate places where 'seq' can be omitted
+
+ construct delegates that point directly at the target method, avoiding an intermediate closure
+ construct delegates that point directly at the target method, avoiding an intermediate closure
+
+ discard pattern in use binding使用バインドでパターンを破棄する
@@ -537,6 +542,11 @@
'as' パターンの右側の非変数パターン
+
+ honor the 'NotNullIfNotNull' attribute on a method's return value
+ honor the 'NotNullIfNotNull' attribute on a method's return value
+
+ nullable optional interopNull 許容のオプションの相互運用
@@ -602,6 +612,11 @@
任意のサイズのリテラルを一覧表示する
+
+ record type and expression spreads
+ record type and expression spreads
+
+ informational messages related to reference cells参照セルに関連する情報メッセージ
@@ -667,11 +682,6 @@
インターフェイス内の静的メンバー
-
- Raises errors on incorrect indentation, allows better recovery and analysis during editing
- 不適切なインデントでエラーが発生し、編集中の回復と分析が向上します
-
- string interpolation文字列の補間
@@ -1117,11 +1127,6 @@
F# インターフェイス情報を含めます。既定値は file です。ライブラリの配布に不可欠です。
-
- Override indentation rules implied by the language version ({0} by default)
- Override indentation rules implied by the language version ({0} by default)
-
- Supported language versions:サポートされる言語バージョン:
@@ -1247,6 +1252,16 @@
メンバー本体が必要です
+
+ Missing spread source expression after '...'.
+ Missing spread source expression after '...'.
+
+
+
+ Missing spread source type after '...'.
+ Missing spread source type after '...'.
+
+ Missing union case name共用体のケース名がありません
@@ -1262,6 +1277,16 @@
プライマリ コンストラクターで使用できるのは単純なパターンのみです
+
+ Spreading is not supported in this construct.
+ Spreading is not supported in this construct.
+
+
+
+ Spreading is not supported in this position. Use one of the forms {{ ...expr1; A = expr2 }} or {{ expr1 with A = expr2 }} instead.
+ Spreading is not supported in this position. Use one of the forms {{ ...expr1; A = expr2 }} or {{ expr1 with A = expr2 }} instead.
+
+ Incomplete declaration of a static construct. Use 'static let','static do','static member' or 'static val' for declaration.静的コンストラクトの不完全な宣言。宣言には、'static let'、'static do'、'static member'、または 'static val' を使用します。
@@ -1482,6 +1507,16 @@
この匿名レコードの種類に、フィールド '{0}' が複数回出現します。
+
+ The source expression of a spread into an anonymous record expression cannot be nullable.
+ The source expression of a spread into an anonymous record expression cannot be nullable.
+
+
+
+ The source expression of a spread into an anonymous record expression must have a nominal or anonymous record type.
+ The source expression of a spread into an anonymous record expression must have a nominal or anonymous record type.
+
+ This attribute is not valid for use on union cases with fields.This attribute is not valid for use on union cases with fields.
@@ -1527,6 +1562,11 @@
Expected unit-of-measure type parameter must be marked with the [<Measure>] attribute.
+
+ Generic attribute types are not supported in F#. The type '{0}' has type parameters and cannot be used as an attribute.
+ Generic attribute types are not supported in F#. The type '{0}' has type parameters and cannot be used as an attribute.
+
+ The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'.構文 'expr1[expr2]' はインデックス作成に使用されます。インデックスを有効にするために型の注釈を追加するか、関数を呼び出す場合には、'expr1 [expr2]' のようにスペースを入れます。
@@ -1767,6 +1807,41 @@
The value or member '{0}' has been marked 'inline' but is part of a recursive binding group. F# does not support recursive 'inline' values. Either remove the 'inline' modifier or refactor the recursion.
+
+ Spread field '{0}' shadows an explicitly declared field with the same name.
+ Spread field '{0}' shadows an explicitly declared field with the same name.
+
+
+
+ The source expression of a spread into a nominal record expression cannot be nullable.
+ The source expression of a spread into a nominal record expression cannot be nullable.
+
+
+
+ The source expression of a spread into a nominal record expression must have a nominal or anonymous record type.
+ The source expression of a spread into a nominal record expression must have a nominal or anonymous record type.
+
+
+
+ Spread expressions and 'with' cannot be used together in the same copy-and-update expression.
+ Spread expressions and 'with' cannot be used together in the same copy-and-update expression.
+
+
+
+ Spread field '{0}' from type '{1}' shadows an explicitly declared field with the same name.
+ Spread field '{0}' from type '{1}' shadows an explicitly declared field with the same name.
+
+
+
+ The source type of a spread into a record type definition cannot be nullable.
+ The source type of a spread into a record type definition cannot be nullable.
+
+
+
+ The source type of a spread into a record type definition must itself be a nominal or anonymous record type.
+ The source type of a spread into a record type definition must itself be a nominal or anonymous record type.
+
+ The 'let! ... and! ...' construct may only be used if the computation expression builder defines either a '{0}' method or appropriate 'MergeSources' and 'Bind' methods'let! ... and! ...' コンストラクトは、コンピュテーション式ビルダーが '{0}' メソッドまたは適切な 'MergeSource' および 'Bind' メソッドのいずれかを定義している場合にのみ使用できます
@@ -1852,6 +1927,11 @@
特性では、オプションの、in 引数、out 引数、ParamArray 引数、CallerInfo 引数、または Quote 引数を指定することはできません
+
+ This type definition involves a cyclic reference through a spread.
+ This type definition involves a cyclic reference through a spread.
+
+ The type '{0}' does not support a nullness qualification.The type '{0}' does not support a nullness qualification.
@@ -6628,8 +6708,8 @@
- Unexpected syntax or possible incorrect indentation: this token is offside of context started at position {0}. Try indenting this further.\nTo continue using non-conforming indentation, pass the '--strict-indentation-' flag to the compiler, or set the language version to F# 7.
- 予期しない構文またはインデントが正しくない可能性: このトークンは位置 {0} から開始されるコンテキストのオフサイドになります。このトークンのインデントを増やしてみてください。\n非準拠のインデントを引き続き使用するには、'--strict-indent-' フラグをコンパイラに渡すか、言語バージョンを F# 7 に設定してください。
+ Unexpected syntax or possible incorrect indentation: this token is offside of context started at position {0}. Try indenting this further.
+ 予期しない構文またはインデントが正しくない可能性: このトークンは位置 {0} から開始されるコンテキストのオフサイドになります。このトークンのインデントを増やしてみてください。\n非準拠のインデントを引き続き使用するには、'--strict-indent-' フラグをコンパイラに渡すか、言語バージョンを F# 7 に設定してください。
diff --git a/src/Compiler/xlf/FSComp.txt.ko.xlf b/src/Compiler/xlf/FSComp.txt.ko.xlf
index ae0bdce0e1f..8040a2c7c16 100644
--- a/src/Compiler/xlf/FSComp.txt.ko.xlf
+++ b/src/Compiler/xlf/FSComp.txt.ko.xlf
@@ -1,4 +1,4 @@
-
+
@@ -367,6 +367,11 @@
Deprecate places where 'seq' can be omitted
+
+ construct delegates that point directly at the target method, avoiding an intermediate closure
+ construct delegates that point directly at the target method, avoiding an intermediate closure
+
+ discard pattern in use binding사용 중인 패턴 바인딩 무시
@@ -537,6 +542,11 @@
'as' 패턴의 오른쪽에 있는 변수가 아닌 패턴
+
+ honor the 'NotNullIfNotNull' attribute on a method's return value
+ honor the 'NotNullIfNotNull' attribute on a method's return value
+
+ nullable optional interopnullable 선택적 interop
@@ -602,6 +612,11 @@
모든 크기의 목록 리터럴
+
+ record type and expression spreads
+ record type and expression spreads
+
+ informational messages related to reference cells참조 셀과 관련된 정보 메시지
@@ -667,11 +682,6 @@
인터페이스의 정적 멤버
-
- Raises errors on incorrect indentation, allows better recovery and analysis during editing
- 잘못된 들여쓰기에 대한 오류를 제기하고 편집 중에 더 나은 복구 및 분석이 가능합니다.
-
- string interpolation문자열 보간
@@ -1117,11 +1127,6 @@
F# 인터페이스 정보를 포함합니다. 기본값은 파일입니다. 라이브러리를 배포하는 데 필수적입니다.
-
- Override indentation rules implied by the language version ({0} by default)
- Override indentation rules implied by the language version ({0} by default)
-
- Supported language versions:지원되는 언어 버전:
@@ -1247,6 +1252,16 @@
멤버 본문이 필요한 경우
+
+ Missing spread source expression after '...'.
+ Missing spread source expression after '...'.
+
+
+
+ Missing spread source type after '...'.
+ Missing spread source type after '...'.
+
+ Missing union case name공용 구조체 대/소문자 이름이 없습니다.
@@ -1262,6 +1277,16 @@
기본 생성자에서는 단순 패턴만 허용됩니다.
+
+ Spreading is not supported in this construct.
+ Spreading is not supported in this construct.
+
+
+
+ Spreading is not supported in this position. Use one of the forms {{ ...expr1; A = expr2 }} or {{ expr1 with A = expr2 }} instead.
+ Spreading is not supported in this position. Use one of the forms {{ ...expr1; A = expr2 }} or {{ expr1 with A = expr2 }} instead.
+
+ Incomplete declaration of a static construct. Use 'static let','static do','static member' or 'static val' for declaration.정적 구문의 선언이 불완전합니다. 선언에 'static let','static do','static member' 또는 'static val'을 사용합니다.
@@ -1482,6 +1507,16 @@
'{0}' 필드가 이 익명 레코드 형식에서 여러 번 나타납니다.
+
+ The source expression of a spread into an anonymous record expression cannot be nullable.
+ The source expression of a spread into an anonymous record expression cannot be nullable.
+
+
+
+ The source expression of a spread into an anonymous record expression must have a nominal or anonymous record type.
+ The source expression of a spread into an anonymous record expression must have a nominal or anonymous record type.
+
+ This attribute is not valid for use on union cases with fields.This attribute is not valid for use on union cases with fields.
@@ -1527,6 +1562,11 @@
Expected unit-of-measure type parameter must be marked with the [<Measure>] attribute.
+
+ Generic attribute types are not supported in F#. The type '{0}' has type parameters and cannot be used as an attribute.
+ Generic attribute types are not supported in F#. The type '{0}' has type parameters and cannot be used as an attribute.
+
+ The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'.인덱싱에는 'expr1[expr2]' 구문이 사용됩니다. 인덱싱을 사용하도록 설정하기 위해 형식 주석을 추가하는 것을 고려하거나 함수를 호출하는 경우 공백을 추가하세요(예: 'expr1 [expr2]').
@@ -1767,6 +1807,41 @@
The value or member '{0}' has been marked 'inline' but is part of a recursive binding group. F# does not support recursive 'inline' values. Either remove the 'inline' modifier or refactor the recursion.
+
+ Spread field '{0}' shadows an explicitly declared field with the same name.
+ Spread field '{0}' shadows an explicitly declared field with the same name.
+
+
+
+ The source expression of a spread into a nominal record expression cannot be nullable.
+ The source expression of a spread into a nominal record expression cannot be nullable.
+
+
+
+ The source expression of a spread into a nominal record expression must have a nominal or anonymous record type.
+ The source expression of a spread into a nominal record expression must have a nominal or anonymous record type.
+
+
+
+ Spread expressions and 'with' cannot be used together in the same copy-and-update expression.
+ Spread expressions and 'with' cannot be used together in the same copy-and-update expression.
+
+
+
+ Spread field '{0}' from type '{1}' shadows an explicitly declared field with the same name.
+ Spread field '{0}' from type '{1}' shadows an explicitly declared field with the same name.
+
+
+
+ The source type of a spread into a record type definition cannot be nullable.
+ The source type of a spread into a record type definition cannot be nullable.
+
+
+
+ The source type of a spread into a record type definition must itself be a nominal or anonymous record type.
+ The source type of a spread into a record type definition must itself be a nominal or anonymous record type.
+
+ The 'let! ... and! ...' construct may only be used if the computation expression builder defines either a '{0}' method or appropriate 'MergeSources' and 'Bind' methods'let! ... and! ...' 구문은 계산 식 작성기에서 '{0}' 메서드 또는 적절한 'MergeSources' 및 'Bind' 메서드를 정의한 경우에만 사용할 수 있습니다.
@@ -1852,6 +1927,11 @@
특성은 optional, in, out, ParamArray, CallerInfo, Quote 인수를 지정할 수 없습니다.
+
+ This type definition involves a cyclic reference through a spread.
+ This type definition involves a cyclic reference through a spread.
+
+ The type '{0}' does not support a nullness qualification.The type '{0}' does not support a nullness qualification.
@@ -6628,8 +6708,8 @@
- Unexpected syntax or possible incorrect indentation: this token is offside of context started at position {0}. Try indenting this further.\nTo continue using non-conforming indentation, pass the '--strict-indentation-' flag to the compiler, or set the language version to F# 7.
- 예기치 않은 구문 또는 잘못된 들여쓰기: 이 토큰은 {0} 위치에서 시작된 컨텍스트의 오프 사이드입니다. 이를 더 들여쓰기해 보세요.\n규정을 준수하지 않는 들여쓰기를 계속 사용하려면 '--strict-indentation-' 플래그를 컴파일러에 전달하거나 언어 버전을 F# 7로 설정합니다.
+ Unexpected syntax or possible incorrect indentation: this token is offside of context started at position {0}. Try indenting this further.
+ 예기치 않은 구문 또는 잘못된 들여쓰기: 이 토큰은 {0} 위치에서 시작된 컨텍스트의 오프 사이드입니다. 이를 더 들여쓰기해 보세요.\n규정을 준수하지 않는 들여쓰기를 계속 사용하려면 '--strict-indentation-' 플래그를 컴파일러에 전달하거나 언어 버전을 F# 7로 설정합니다.
diff --git a/src/Compiler/xlf/FSComp.txt.pl.xlf b/src/Compiler/xlf/FSComp.txt.pl.xlf
index e7f9fbedc3e..82fb9e683d5 100644
--- a/src/Compiler/xlf/FSComp.txt.pl.xlf
+++ b/src/Compiler/xlf/FSComp.txt.pl.xlf
@@ -1,4 +1,4 @@
-
+
@@ -367,6 +367,11 @@
Deprecate places where 'seq' can be omitted
+
+ construct delegates that point directly at the target method, avoiding an intermediate closure
+ construct delegates that point directly at the target method, avoiding an intermediate closure
+
+ discard pattern in use bindingodrzuć wzorzec w powiązaniu użycia
@@ -537,6 +542,11 @@
stałe wzorce po prawej stronie wzorców typu „as”
+
+ honor the 'NotNullIfNotNull' attribute on a method's return value
+ honor the 'NotNullIfNotNull' attribute on a method's return value
+
+ nullable optional interopopcjonalna międzyoperacyjność dopuszczająca wartość null
@@ -602,6 +612,11 @@
wyświetlanie na liście literałów o dowolnym rozmiarze
+
+ record type and expression spreads
+ record type and expression spreads
+
+ informational messages related to reference cellskomunikaty informacyjne związane z odwołaniami do komórek
@@ -667,11 +682,6 @@
Statyczne składowe w interfejsach
-
- Raises errors on incorrect indentation, allows better recovery and analysis during editing
- Zgłasza błędy w przypadku nieprawidłowego wcięcia, umożliwia lepsze odzyskiwanie i analizę podczas edytowania
-
- string interpolationinterpolacja ciągu
@@ -1117,11 +1127,6 @@
Uwzględnij informacje o interfejsie języka F#. Wartość domyślna to plik. Niezbędne do rozpowszechniania bibliotek.
-
- Override indentation rules implied by the language version ({0} by default)
- Override indentation rules implied by the language version ({0} by default)
-
- Supported language versions:Obsługiwane wersje językowe:
@@ -1247,6 +1252,16 @@
Oczekiwano treści elementu członkowskiego
+
+ Missing spread source expression after '...'.
+ Missing spread source expression after '...'.
+
+
+
+ Missing spread source type after '...'.
+ Missing spread source type after '...'.
+
+ Missing union case nameBrak nazwy przypadku unii
@@ -1262,6 +1277,16 @@
Tylko proste wzorce są dozwolone w konstruktorach podstawowych
+
+ Spreading is not supported in this construct.
+ Spreading is not supported in this construct.
+
+
+
+ Spreading is not supported in this position. Use one of the forms {{ ...expr1; A = expr2 }} or {{ expr1 with A = expr2 }} instead.
+ Spreading is not supported in this position. Use one of the forms {{ ...expr1; A = expr2 }} or {{ expr1 with A = expr2 }} instead.
+
+ Incomplete declaration of a static construct. Use 'static let','static do','static member' or 'static val' for declaration.Niekompletna deklaracja konstrukcji statycznej. Użyj elementu „static let”, „static do”, „static member” lub „static val” na potrzeby deklaracji.
@@ -1482,6 +1507,16 @@
Pole „{0}” występuje wielokrotnie w tym anonimowym typie rekordu.
+
+ The source expression of a spread into an anonymous record expression cannot be nullable.
+ The source expression of a spread into an anonymous record expression cannot be nullable.
+
+
+
+ The source expression of a spread into an anonymous record expression must have a nominal or anonymous record type.
+ The source expression of a spread into an anonymous record expression must have a nominal or anonymous record type.
+
+ This attribute is not valid for use on union cases with fields.This attribute is not valid for use on union cases with fields.
@@ -1527,6 +1562,11 @@
Expected unit-of-measure type parameter must be marked with the [<Measure>] attribute.
+
+ Generic attribute types are not supported in F#. The type '{0}' has type parameters and cannot be used as an attribute.
+ Generic attribute types are not supported in F#. The type '{0}' has type parameters and cannot be used as an attribute.
+
+ The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'.Do indeksowania używana jest składnia „expr1[expr2]”. Rozważ dodanie adnotacji typu, aby umożliwić indeksowanie, lub jeśli wywołujesz funkcję dodaj spację, np. „expr1 [expr2]”.
@@ -1767,6 +1807,41 @@
The value or member '{0}' has been marked 'inline' but is part of a recursive binding group. F# does not support recursive 'inline' values. Either remove the 'inline' modifier or refactor the recursion.
+
+ Spread field '{0}' shadows an explicitly declared field with the same name.
+ Spread field '{0}' shadows an explicitly declared field with the same name.
+
+
+
+ The source expression of a spread into a nominal record expression cannot be nullable.
+ The source expression of a spread into a nominal record expression cannot be nullable.
+
+
+
+ The source expression of a spread into a nominal record expression must have a nominal or anonymous record type.
+ The source expression of a spread into a nominal record expression must have a nominal or anonymous record type.
+
+
+
+ Spread expressions and 'with' cannot be used together in the same copy-and-update expression.
+ Spread expressions and 'with' cannot be used together in the same copy-and-update expression.
+
+
+
+ Spread field '{0}' from type '{1}' shadows an explicitly declared field with the same name.
+ Spread field '{0}' from type '{1}' shadows an explicitly declared field with the same name.
+
+
+
+ The source type of a spread into a record type definition cannot be nullable.
+ The source type of a spread into a record type definition cannot be nullable.
+
+
+
+ The source type of a spread into a record type definition must itself be a nominal or anonymous record type.
+ The source type of a spread into a record type definition must itself be a nominal or anonymous record type.
+
+ The 'let! ... and! ...' construct may only be used if the computation expression builder defines either a '{0}' method or appropriate 'MergeSources' and 'Bind' methodsKonstrukcji „let! ... and! ...” można użyć tylko wtedy, gdy konstruktor wyrażeń obliczeniowych definiuje metodę „{0}” lub odpowiednie metody „MergeSource” i „Bind”
@@ -1852,6 +1927,11 @@
Cecha nie może określać opcjonalnych argumentów in, out, ParamArray, CallerInfo lub Quote
+
+ This type definition involves a cyclic reference through a spread.
+ This type definition involves a cyclic reference through a spread.
+
+ The type '{0}' does not support a nullness qualification.The type '{0}' does not support a nullness qualification.
@@ -6628,8 +6708,8 @@
- Unexpected syntax or possible incorrect indentation: this token is offside of context started at position {0}. Try indenting this further.\nTo continue using non-conforming indentation, pass the '--strict-indentation-' flag to the compiler, or set the language version to F# 7.
- Nieoczekiwana składnia lub możliwe niepoprawne wcięcie: ten token jest poza kontekstem uruchomionym na pozycji {0}. Spróbuj jeszcze bardziej wciąć to ustawienie.\nAby kontynuować używanie niezgodnych wcięć, przekaż flagę „--strict-indentation-” do kompilatora lub ustaw wersję języka na F# 7.
+ Unexpected syntax or possible incorrect indentation: this token is offside of context started at position {0}. Try indenting this further.
+ Nieoczekiwana składnia lub możliwe niepoprawne wcięcie: ten token jest poza kontekstem uruchomionym na pozycji {0}. Spróbuj jeszcze bardziej wciąć to ustawienie.\nAby kontynuować używanie niezgodnych wcięć, przekaż flagę „--strict-indentation-” do kompilatora lub ustaw wersję języka na F# 7.
diff --git a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf
index 2ee0777fb1b..b369e181e5a 100644
--- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf
+++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf
@@ -1,4 +1,4 @@
-
+
@@ -367,6 +367,11 @@
Deprecate places where 'seq' can be omitted
+
+ construct delegates that point directly at the target method, avoiding an intermediate closure
+ construct delegates that point directly at the target method, avoiding an intermediate closure
+
+ discard pattern in use bindingdescartar o padrão em uso de associação
@@ -537,6 +542,11 @@
padrões não-variáveis à direita dos padrões 'as'.
+
+ honor the 'NotNullIfNotNull' attribute on a method's return value
+ honor the 'NotNullIfNotNull' attribute on a method's return value
+
+ nullable optional interopinteroperabilidade opcional anulável
@@ -602,6 +612,11 @@
literais de lista de qualquer tamanho
+
+ record type and expression spreads
+ record type and expression spreads
+
+ informational messages related to reference cellsmensagens informativas relacionadas a células de referência
@@ -667,11 +682,6 @@
Membros estáticos em interfaces
-
- Raises errors on incorrect indentation, allows better recovery and analysis during editing
- Gera erros de recuo incorreto, permite uma melhor recuperação e análise durante a edição
-
- string interpolationinterpolação da cadeia de caracteres
@@ -1117,11 +1127,6 @@
Inclua informações da interface F#, o padrão é file. Essencial para distribuir bibliotecas.
-
- Override indentation rules implied by the language version ({0} by default)
- Override indentation rules implied by the language version ({0} by default)
-
- Supported language versions:Versões de linguagens com suporte:
@@ -1247,6 +1252,16 @@
Esperando corpo do membro
+
+ Missing spread source expression after '...'.
+ Missing spread source expression after '...'.
+
+
+
+ Missing spread source type after '...'.
+ Missing spread source type after '...'.
+
+ Missing union case nameNome do caso de união ausente
@@ -1262,6 +1277,16 @@
Somente padrões simples são permitidos em construtores primários
+
+ Spreading is not supported in this construct.
+ Spreading is not supported in this construct.
+
+
+
+ Spreading is not supported in this position. Use one of the forms {{ ...expr1; A = expr2 }} or {{ expr1 with A = expr2 }} instead.
+ Spreading is not supported in this position. Use one of the forms {{ ...expr1; A = expr2 }} or {{ expr1 with A = expr2 }} instead.
+
+ Incomplete declaration of a static construct. Use 'static let','static do','static member' or 'static val' for declaration.Declaração incompleta de um constructo estático. Use "static let","static do","static member" ou "static val" para declaração.
@@ -1482,6 +1507,16 @@
O campo '{0}' aparece várias vezes nesse tipo de registro anônimo.
+
+ The source expression of a spread into an anonymous record expression cannot be nullable.
+ The source expression of a spread into an anonymous record expression cannot be nullable.
+
+
+
+ The source expression of a spread into an anonymous record expression must have a nominal or anonymous record type.
+ The source expression of a spread into an anonymous record expression must have a nominal or anonymous record type.
+
+ This attribute is not valid for use on union cases with fields.This attribute is not valid for use on union cases with fields.
@@ -1527,6 +1562,11 @@
Expected unit-of-measure type parameter must be marked with the [<Measure>] attribute.
+
+ Generic attribute types are not supported in F#. The type '{0}' has type parameters and cannot be used as an attribute.
+ Generic attribute types are not supported in F#. The type '{0}' has type parameters and cannot be used as an attribute.
+
+ The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'.A sintaxe 'expr1[expr2]' é usada para indexação. Considere adicionar uma anotação de tipo para habilitar a indexação ou, se chamar uma função, adicione um espaço, por exemplo, 'expr1 [expr2]'.
@@ -1767,6 +1807,41 @@
The value or member '{0}' has been marked 'inline' but is part of a recursive binding group. F# does not support recursive 'inline' values. Either remove the 'inline' modifier or refactor the recursion.
+
+ Spread field '{0}' shadows an explicitly declared field with the same name.
+ Spread field '{0}' shadows an explicitly declared field with the same name.
+
+
+
+ The source expression of a spread into a nominal record expression cannot be nullable.
+ The source expression of a spread into a nominal record expression cannot be nullable.
+
+
+
+ The source expression of a spread into a nominal record expression must have a nominal or anonymous record type.
+ The source expression of a spread into a nominal record expression must have a nominal or anonymous record type.
+
+
+
+ Spread expressions and 'with' cannot be used together in the same copy-and-update expression.
+ Spread expressions and 'with' cannot be used together in the same copy-and-update expression.
+
+
+
+ Spread field '{0}' from type '{1}' shadows an explicitly declared field with the same name.
+ Spread field '{0}' from type '{1}' shadows an explicitly declared field with the same name.
+
+
+
+ The source type of a spread into a record type definition cannot be nullable.
+ The source type of a spread into a record type definition cannot be nullable.
+
+
+
+ The source type of a spread into a record type definition must itself be a nominal or anonymous record type.
+ The source type of a spread into a record type definition must itself be a nominal or anonymous record type.
+
+ The 'let! ... and! ...' construct may only be used if the computation expression builder defines either a '{0}' method or appropriate 'MergeSources' and 'Bind' methodsO “let! ... and! ...” só poderá ser usada se o construtor de expressão de cálculo definir um método “{0}” ou métodos “MergeSources” e “Bind” apropriados
@@ -1852,6 +1927,11 @@
Uma característica não pode especificar os argumentos optional, in, out, ParamArray, CallerInfo ou Quote
+
+ This type definition involves a cyclic reference through a spread.
+ This type definition involves a cyclic reference through a spread.
+
+ The type '{0}' does not support a nullness qualification.The type '{0}' does not support a nullness qualification.
@@ -6628,8 +6708,8 @@
- Unexpected syntax or possible incorrect indentation: this token is offside of context started at position {0}. Try indenting this further.\nTo continue using non-conforming indentation, pass the '--strict-indentation-' flag to the compiler, or set the language version to F# 7.
- Sintaxe inesperada ou possível recuo incorreto: esse token está fora do contexto iniciado na posição {0}. Tente recuar isso ainda mais.\nPara continuar usando o recuo não compatível, passe o sinalizador '--strict-indentation-' para o compilador ou defina a versão da linguagem como F# 7.
+ Unexpected syntax or possible incorrect indentation: this token is offside of context started at position {0}. Try indenting this further.
+ Sintaxe inesperada ou possível recuo incorreto: esse token está fora do contexto iniciado na posição {0}. Tente recuar isso ainda mais.\nPara continuar usando o recuo não compatível, passe o sinalizador '--strict-indentation-' para o compilador ou defina a versão da linguagem como F# 7.
diff --git a/src/Compiler/xlf/FSComp.txt.ru.xlf b/src/Compiler/xlf/FSComp.txt.ru.xlf
index 4b425932b82..a8a6f7923e1 100644
--- a/src/Compiler/xlf/FSComp.txt.ru.xlf
+++ b/src/Compiler/xlf/FSComp.txt.ru.xlf
@@ -1,4 +1,4 @@
-
+
@@ -367,6 +367,11 @@
Deprecate places where 'seq' can be omitted
+
+ construct delegates that point directly at the target method, avoiding an intermediate closure
+ construct delegates that point directly at the target method, avoiding an intermediate closure
+
+ discard pattern in use bindingшаблон отмены в привязке использования
@@ -537,6 +542,11 @@
шаблоны без переменных справа от шаблонов "as"
+
+ honor the 'NotNullIfNotNull' attribute on a method's return value
+ honor the 'NotNullIfNotNull' attribute on a method's return value
+
+ nullable optional interopнеобязательное взаимодействие, допускающее значение NULL
@@ -602,6 +612,11 @@
список литералов любого размера
+
+ record type and expression spreads
+ record type and expression spreads
+
+ informational messages related to reference cellsинформационные сообщения, связанные с ссылочными ячейками
@@ -667,11 +682,6 @@
Статические элементы в интерфейсах
-
- Raises errors on incorrect indentation, allows better recovery and analysis during editing
- Выдает ошибки при неправильном отступе, обеспечивает более эффективное восстановление и анализ во время редактирования
-
- string interpolationинтерполяция строк
@@ -1117,11 +1127,6 @@
Включить сведения об интерфейсе F#, по умолчанию используется файл. Необходимо для распространения библиотек.
-
- Override indentation rules implied by the language version ({0} by default)
- Override indentation rules implied by the language version ({0} by default)
-
- Supported language versions:Поддерживаемые языковые версии:
@@ -1247,6 +1252,16 @@
Требуется текст сообщения элемента
+
+ Missing spread source expression after '...'.
+ Missing spread source expression after '...'.
+
+
+
+ Missing spread source type after '...'.
+ Missing spread source type after '...'.
+
+ Missing union case nameОтсутствует имя случая объединения
@@ -1262,6 +1277,16 @@
В первичных конструкторах разрешены только простые шаблоны
+
+ Spreading is not supported in this construct.
+ Spreading is not supported in this construct.
+
+
+
+ Spreading is not supported in this position. Use one of the forms {{ ...expr1; A = expr2 }} or {{ expr1 with A = expr2 }} instead.
+ Spreading is not supported in this position. Use one of the forms {{ ...expr1; A = expr2 }} or {{ expr1 with A = expr2 }} instead.
+
+ Incomplete declaration of a static construct. Use 'static let','static do','static member' or 'static val' for declaration.Неполное объявление статической конструкции. Для объявления используйте «static let», «static do», «staticmember» или «static val».
@@ -1482,6 +1507,16 @@
Поле "{0}" появляется несколько раз в этом типе анонимной записи.
+
+ The source expression of a spread into an anonymous record expression cannot be nullable.
+ The source expression of a spread into an anonymous record expression cannot be nullable.
+
+
+
+ The source expression of a spread into an anonymous record expression must have a nominal or anonymous record type.
+ The source expression of a spread into an anonymous record expression must have a nominal or anonymous record type.
+
+ This attribute is not valid for use on union cases with fields.This attribute is not valid for use on union cases with fields.
@@ -1527,6 +1562,11 @@
Expected unit-of-measure type parameter must be marked with the [<Measure>] attribute.
+
+ Generic attribute types are not supported in F#. The type '{0}' has type parameters and cannot be used as an attribute.
+ Generic attribute types are not supported in F#. The type '{0}' has type parameters and cannot be used as an attribute.
+
+ The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'.Для индексирования используется синтаксис "expr1[expr2]". Рассмотрите возможность добавления аннотации типа для включения индексации или при вызове функции добавьте пробел, например "expr1 [expr2]".
@@ -1767,6 +1807,41 @@
The value or member '{0}' has been marked 'inline' but is part of a recursive binding group. F# does not support recursive 'inline' values. Either remove the 'inline' modifier or refactor the recursion.
+
+ Spread field '{0}' shadows an explicitly declared field with the same name.
+ Spread field '{0}' shadows an explicitly declared field with the same name.
+
+
+
+ The source expression of a spread into a nominal record expression cannot be nullable.
+ The source expression of a spread into a nominal record expression cannot be nullable.
+
+
+
+ The source expression of a spread into a nominal record expression must have a nominal or anonymous record type.
+ The source expression of a spread into a nominal record expression must have a nominal or anonymous record type.
+
+
+
+ Spread expressions and 'with' cannot be used together in the same copy-and-update expression.
+ Spread expressions and 'with' cannot be used together in the same copy-and-update expression.
+
+
+
+ Spread field '{0}' from type '{1}' shadows an explicitly declared field with the same name.
+ Spread field '{0}' from type '{1}' shadows an explicitly declared field with the same name.
+
+
+
+ The source type of a spread into a record type definition cannot be nullable.
+ The source type of a spread into a record type definition cannot be nullable.
+
+
+
+ The source type of a spread into a record type definition must itself be a nominal or anonymous record type.
+ The source type of a spread into a record type definition must itself be a nominal or anonymous record type.
+
+ The 'let! ... and! ...' construct may only be used if the computation expression builder defines either a '{0}' method or appropriate 'MergeSources' and 'Bind' methodsКонструкцию "let! ... and! ..." можно использовать только в том случае, если построитель выражений с вычислениями определяет либо метод "{0}", либо соответствующие методы "MergeSources" и "Bind"
@@ -1852,6 +1927,11 @@
Признак не может указывать необязательные аргументы in, out, ParamArray, CallerInfo или Quote
+
+ This type definition involves a cyclic reference through a spread.
+ This type definition involves a cyclic reference through a spread.
+
+ The type '{0}' does not support a nullness qualification.The type '{0}' does not support a nullness qualification.
@@ -6628,8 +6708,8 @@
- Unexpected syntax or possible incorrect indentation: this token is offside of context started at position {0}. Try indenting this further.\nTo continue using non-conforming indentation, pass the '--strict-indentation-' flag to the compiler, or set the language version to F# 7.
- Неожиданный синтаксис или, возможно, неправильный отступ: этот токен находится вне контекста, начатого в позиции {0}. Попробуйте увеличить отступ.\nЧтобы продолжить использование несоответствующего отступа, передайте компилятору флаг '--strict-indentation-' или установите версию языка F# 7.
+ Unexpected syntax or possible incorrect indentation: this token is offside of context started at position {0}. Try indenting this further.
+ Неожиданный синтаксис или, возможно, неправильный отступ: этот токен находится вне контекста, начатого в позиции {0}. Попробуйте увеличить отступ.\nЧтобы продолжить использование несоответствующего отступа, передайте компилятору флаг '--strict-indentation-' или установите версию языка F# 7.
diff --git a/src/Compiler/xlf/FSComp.txt.tr.xlf b/src/Compiler/xlf/FSComp.txt.tr.xlf
index 851fc063da5..74f800138e0 100644
--- a/src/Compiler/xlf/FSComp.txt.tr.xlf
+++ b/src/Compiler/xlf/FSComp.txt.tr.xlf
@@ -1,4 +1,4 @@
-
+
@@ -367,6 +367,11 @@
Deprecate places where 'seq' can be omitted
+
+ construct delegates that point directly at the target method, avoiding an intermediate closure
+ construct delegates that point directly at the target method, avoiding an intermediate closure
+
+ discard pattern in use bindingkullanım bağlamasında deseni at
@@ -537,6 +542,11 @@
'as' desenlerinin sağındaki değişken olmayan desenler
+
+ honor the 'NotNullIfNotNull' attribute on a method's return value
+ honor the 'NotNullIfNotNull' attribute on a method's return value
+
+ nullable optional interopnull atanabilir isteğe bağlı birlikte çalışma
@@ -602,6 +612,11 @@
tüm boyutlardaki sabit değerleri listele
+
+ record type and expression spreads
+ record type and expression spreads
+
+ informational messages related to reference cellsbaşvuru hücreleriyle ilgili bilgi mesajları
@@ -667,11 +682,6 @@
Arabirimlerdeki statik üyeler
-
- Raises errors on incorrect indentation, allows better recovery and analysis during editing
- Yanlış girinti üzerine hata verir ve düzenleme sırasında daha iyi kurtarma ve analize olanak sağlar
-
- string interpolationdizede düz metin arasına kod ekleme
@@ -1117,11 +1127,6 @@
F# arabirim bilgilerini dahil edin; varsayılan değer dosyadır. Kitaplıkları dağıtmak için gereklidir.
-
- Override indentation rules implied by the language version ({0} by default)
- Override indentation rules implied by the language version ({0} by default)
-
- Supported language versions:Desteklenen dil sürümleri:
@@ -1247,6 +1252,16 @@
Üye gövdesi bekleniyor
+
+ Missing spread source expression after '...'.
+ Missing spread source expression after '...'.
+
+
+
+ Missing spread source type after '...'.
+ Missing spread source type after '...'.
+
+ Missing union case nameBirleşim durumu adı eksik
@@ -1262,6 +1277,16 @@
Birincil oluşturucularda yalnızca basit desenlere izin verilir
+
+ Spreading is not supported in this construct.
+ Spreading is not supported in this construct.
+
+
+
+ Spreading is not supported in this position. Use one of the forms {{ ...expr1; A = expr2 }} or {{ expr1 with A = expr2 }} instead.
+ Spreading is not supported in this position. Use one of the forms {{ ...expr1; A = expr2 }} or {{ expr1 with A = expr2 }} instead.
+
+ Incomplete declaration of a static construct. Use 'static let','static do','static member' or 'static val' for declaration.Statik yapının bildirimi eksik. Bildirim için 'static let','static do','static member' veya 'static val' kullanın.
@@ -1482,6 +1507,16 @@
'{0}' alanı bu anonim kayıt türünde birden fazla yerde görünüyor.
+
+ The source expression of a spread into an anonymous record expression cannot be nullable.
+ The source expression of a spread into an anonymous record expression cannot be nullable.
+
+
+
+ The source expression of a spread into an anonymous record expression must have a nominal or anonymous record type.
+ The source expression of a spread into an anonymous record expression must have a nominal or anonymous record type.
+
+ This attribute is not valid for use on union cases with fields.This attribute is not valid for use on union cases with fields.
@@ -1527,6 +1562,11 @@
Expected unit-of-measure type parameter must be marked with the [<Measure>] attribute.
+
+ Generic attribute types are not supported in F#. The type '{0}' has type parameters and cannot be used as an attribute.
+ Generic attribute types are not supported in F#. The type '{0}' has type parameters and cannot be used as an attribute.
+
+ The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'.Söz dizimi “expr1[expr2]” dizin oluşturma için kullanılıyor. Dizin oluşturmayı etkinleştirmek için bir tür ek açıklama eklemeyi düşünün veya bir işlev çağırıyorsanız bir boşluk ekleyin, örn. “expr1 [expr2]”.
@@ -1767,6 +1807,41 @@
The value or member '{0}' has been marked 'inline' but is part of a recursive binding group. F# does not support recursive 'inline' values. Either remove the 'inline' modifier or refactor the recursion.
+
+ Spread field '{0}' shadows an explicitly declared field with the same name.
+ Spread field '{0}' shadows an explicitly declared field with the same name.
+
+
+
+ The source expression of a spread into a nominal record expression cannot be nullable.
+ The source expression of a spread into a nominal record expression cannot be nullable.
+
+
+
+ The source expression of a spread into a nominal record expression must have a nominal or anonymous record type.
+ The source expression of a spread into a nominal record expression must have a nominal or anonymous record type.
+
+
+
+ Spread expressions and 'with' cannot be used together in the same copy-and-update expression.
+ Spread expressions and 'with' cannot be used together in the same copy-and-update expression.
+
+
+
+ Spread field '{0}' from type '{1}' shadows an explicitly declared field with the same name.
+ Spread field '{0}' from type '{1}' shadows an explicitly declared field with the same name.
+
+
+
+ The source type of a spread into a record type definition cannot be nullable.
+ The source type of a spread into a record type definition cannot be nullable.
+
+
+
+ The source type of a spread into a record type definition must itself be a nominal or anonymous record type.
+ The source type of a spread into a record type definition must itself be a nominal or anonymous record type.
+
+ The 'let! ... and! ...' construct may only be used if the computation expression builder defines either a '{0}' method or appropriate 'MergeSources' and 'Bind' methods'let! ... and! ...' yapısı, yalnızca hesaplama ifadesi oluşturucu bir '{0}' metodunu ya da uygun 'MergeSources' ve 'Bind' metotlarını tanımlarsa kullanılabilir
@@ -1852,6 +1927,11 @@
Bir nitelik optional, in, out, ParamArray, CallerInfo veya Quote bağımsız değişkenlerini belirtemiyor
+
+ This type definition involves a cyclic reference through a spread.
+ This type definition involves a cyclic reference through a spread.
+
+ The type '{0}' does not support a nullness qualification.The type '{0}' does not support a nullness qualification.
@@ -6628,8 +6708,8 @@
- Unexpected syntax or possible incorrect indentation: this token is offside of context started at position {0}. Try indenting this further.\nTo continue using non-conforming indentation, pass the '--strict-indentation-' flag to the compiler, or set the language version to F# 7.
- Beklenmeyen sözdizimi veya olası yanlış girinti: Bu belirteç, {0} konumunda başlayan bağlamın ofsaytıdır. Bunu daha fazla girintilemeyi deneyin.\nUygun olmayan girintiyi kullanmaya devam etmek için '--strict-indentation-' işaretini derleyiciye iletin veya dil sürümünü F# 7 olarak ayarlayın.
+ Unexpected syntax or possible incorrect indentation: this token is offside of context started at position {0}. Try indenting this further.
+ Beklenmeyen sözdizimi veya olası yanlış girinti: Bu belirteç, {0} konumunda başlayan bağlamın ofsaytıdır. Bunu daha fazla girintilemeyi deneyin.\nUygun olmayan girintiyi kullanmaya devam etmek için '--strict-indentation-' işaretini derleyiciye iletin veya dil sürümünü F# 7 olarak ayarlayın.
diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf
index 589fc4eac1a..8477219f669 100644
--- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf
+++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf
@@ -1,4 +1,4 @@
-
+
@@ -367,6 +367,11 @@
Deprecate places where 'seq' can be omitted
+
+ construct delegates that point directly at the target method, avoiding an intermediate closure
+ construct delegates that point directly at the target method, avoiding an intermediate closure
+
+ discard pattern in use binding放弃使用绑定模式
@@ -537,6 +542,11 @@
"as" 模式右侧的非变量模式
+
+ honor the 'NotNullIfNotNull' attribute on a method's return value
+ honor the 'NotNullIfNotNull' attribute on a method's return value
+
+ nullable optional interop可以为 null 的可选互操作
@@ -602,6 +612,11 @@
列出任何大小的文本
+
+ record type and expression spreads
+ record type and expression spreads
+
+ informational messages related to reference cells与引用单元格相关的信息性消息
@@ -667,11 +682,6 @@
接口中的静态成员
-
- Raises errors on incorrect indentation, allows better recovery and analysis during editing
- 在缩进不准确时引发错误,以便在编辑期间更好地恢复和分析
-
- string interpolation字符串内插
@@ -1117,11 +1127,6 @@
包括 F# 接口信息,默认值为文件。对于分发库必不可少。
-
- Override indentation rules implied by the language version ({0} by default)
- Override indentation rules implied by the language version ({0} by default)
-
- Supported language versions:支持的语言版本:
@@ -1247,6 +1252,16 @@
预期成员正文
+
+ Missing spread source expression after '...'.
+ Missing spread source expression after '...'.
+
+
+
+ Missing spread source type after '...'.
+ Missing spread source type after '...'.
+
+ Missing union case name缺少联合用例名称
@@ -1262,6 +1277,16 @@
主构造函数中只允许使用简单模式
+
+ Spreading is not supported in this construct.
+ Spreading is not supported in this construct.
+
+
+
+ Spreading is not supported in this position. Use one of the forms {{ ...expr1; A = expr2 }} or {{ expr1 with A = expr2 }} instead.
+ Spreading is not supported in this position. Use one of the forms {{ ...expr1; A = expr2 }} or {{ expr1 with A = expr2 }} instead.
+
+ Incomplete declaration of a static construct. Use 'static let','static do','static member' or 'static val' for declaration.静态构造的声明不完整。使用“static let”、“static do”、“static member”或“static val”进行声明。
@@ -1482,6 +1507,16 @@
字段“{0}”在此匿名记录类型中多次出现。
+
+ The source expression of a spread into an anonymous record expression cannot be nullable.
+ The source expression of a spread into an anonymous record expression cannot be nullable.
+
+
+
+ The source expression of a spread into an anonymous record expression must have a nominal or anonymous record type.
+ The source expression of a spread into an anonymous record expression must have a nominal or anonymous record type.
+
+ This attribute is not valid for use on union cases with fields.This attribute is not valid for use on union cases with fields.
@@ -1527,6 +1562,11 @@
Expected unit-of-measure type parameter must be marked with the [<Measure>] attribute.
+
+ Generic attribute types are not supported in F#. The type '{0}' has type parameters and cannot be used as an attribute.
+ Generic attribute types are not supported in F#. The type '{0}' has type parameters and cannot be used as an attribute.
+
+ The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'.语法“expr1[expr2]”用于索引。考虑添加类型批注来启用索引,或者在调用函数添加空格,例如“expr1 [expr2]”。
@@ -1767,6 +1807,41 @@
The value or member '{0}' has been marked 'inline' but is part of a recursive binding group. F# does not support recursive 'inline' values. Either remove the 'inline' modifier or refactor the recursion.
+
+ Spread field '{0}' shadows an explicitly declared field with the same name.
+ Spread field '{0}' shadows an explicitly declared field with the same name.
+
+
+
+ The source expression of a spread into a nominal record expression cannot be nullable.
+ The source expression of a spread into a nominal record expression cannot be nullable.
+
+
+
+ The source expression of a spread into a nominal record expression must have a nominal or anonymous record type.
+ The source expression of a spread into a nominal record expression must have a nominal or anonymous record type.
+
+
+
+ Spread expressions and 'with' cannot be used together in the same copy-and-update expression.
+ Spread expressions and 'with' cannot be used together in the same copy-and-update expression.
+
+
+
+ Spread field '{0}' from type '{1}' shadows an explicitly declared field with the same name.
+ Spread field '{0}' from type '{1}' shadows an explicitly declared field with the same name.
+
+
+
+ The source type of a spread into a record type definition cannot be nullable.
+ The source type of a spread into a record type definition cannot be nullable.
+
+
+
+ The source type of a spread into a record type definition must itself be a nominal or anonymous record type.
+ The source type of a spread into a record type definition must itself be a nominal or anonymous record type.
+
+ The 'let! ... and! ...' construct may only be used if the computation expression builder defines either a '{0}' method or appropriate 'MergeSources' and 'Bind' methods仅当计算表达式生成器定义了 "{0}" 方法或适当的 "MergeSources" 和 "Bind" 方法时,才可以使用 "let! ... and! ..." 构造
@@ -1852,6 +1927,11 @@
特征不能指定 option、in、out、ParamArray、CallerInfo 或 Quote 参数
+
+ This type definition involves a cyclic reference through a spread.
+ This type definition involves a cyclic reference through a spread.
+
+ The type '{0}' does not support a nullness qualification.The type '{0}' does not support a nullness qualification.
@@ -6628,8 +6708,8 @@
- Unexpected syntax or possible incorrect indentation: this token is offside of context started at position {0}. Try indenting this further.\nTo continue using non-conforming indentation, pass the '--strict-indentation-' flag to the compiler, or set the language version to F# 7.
- 意外语法或可能错误的缩进: 此令牌对于 {0} 处开始的上下文来说越位。尝试进一步缩进此内容。\n若要继续使用不符合条件的索引,请将 "--strict-indentation-" 传递给编译器,或者将语言版本设置为 F# 7。
+ Unexpected syntax or possible incorrect indentation: this token is offside of context started at position {0}. Try indenting this further.
+ 意外语法或可能错误的缩进: 此令牌对于 {0} 处开始的上下文来说越位。尝试进一步缩进此内容。\n若要继续使用不符合条件的索引,请将 "--strict-indentation-" 传递给编译器,或者将语言版本设置为 F# 7。
diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf
index e3f84137cdb..e791722cb90 100644
--- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf
+++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf
@@ -1,4 +1,4 @@
-
+
@@ -367,6 +367,11 @@
Deprecate places where 'seq' can be omitted
+
+ construct delegates that point directly at the target method, avoiding an intermediate closure
+ construct delegates that point directly at the target method, avoiding an intermediate closure
+
+ discard pattern in use binding捨棄使用繫結中的模式
@@ -537,6 +542,11 @@
'as' 模式右邊的非變數模式
+
+ honor the 'NotNullIfNotNull' attribute on a method's return value
+ honor the 'NotNullIfNotNull' attribute on a method's return value
+
+ nullable optional interop可為 Null 的選擇性 Interop
@@ -602,6 +612,11 @@
列出任何大小的常值
+
+ record type and expression spreads
+ record type and expression spreads
+
+ informational messages related to reference cells與參考儲存格相關的資訊訊息
@@ -667,11 +682,6 @@
介面中的靜態成員
-
- Raises errors on incorrect indentation, allows better recovery and analysis during editing
- 縮排不正確時引發錯誤,以便在編輯期間進行更好的復原和分析
-
- string interpolation字串內插補點
@@ -1117,11 +1127,6 @@
包含 F# 介面資訊,預設值為檔案。發佈程式庫的基本功能。
-
- Override indentation rules implied by the language version ({0} by default)
- Override indentation rules implied by the language version ({0} by default)
-
- Supported language versions:支援的語言版本:
@@ -1247,6 +1252,16 @@
必須是成員主體
+
+ Missing spread source expression after '...'.
+ Missing spread source expression after '...'.
+
+
+
+ Missing spread source type after '...'.
+ Missing spread source type after '...'.
+
+ Missing union case name遺漏聯集案例名稱
@@ -1262,6 +1277,16 @@
主要建構函式中只允許簡單模式
+
+ Spreading is not supported in this construct.
+ Spreading is not supported in this construct.
+
+
+
+ Spreading is not supported in this position. Use one of the forms {{ ...expr1; A = expr2 }} or {{ expr1 with A = expr2 }} instead.
+ Spreading is not supported in this position. Use one of the forms {{ ...expr1; A = expr2 }} or {{ expr1 with A = expr2 }} instead.
+
+ Incomplete declaration of a static construct. Use 'static let','static do','static member' or 'static val' for declaration.不完整的靜態建構宣告。使用 'static let'、'static do'、'static member' 或 'static val' 進行宣告。
@@ -1482,6 +1507,16 @@
欄位 '{0}' 在這個匿名記錄類型中出現多次。
+
+ The source expression of a spread into an anonymous record expression cannot be nullable.
+ The source expression of a spread into an anonymous record expression cannot be nullable.
+
+
+
+ The source expression of a spread into an anonymous record expression must have a nominal or anonymous record type.
+ The source expression of a spread into an anonymous record expression must have a nominal or anonymous record type.
+
+ This attribute is not valid for use on union cases with fields.This attribute is not valid for use on union cases with fields.
@@ -1527,6 +1562,11 @@
Expected unit-of-measure type parameter must be marked with the [<Measure>] attribute.
+
+ Generic attribute types are not supported in F#. The type '{0}' has type parameters and cannot be used as an attribute.
+ Generic attribute types are not supported in F#. The type '{0}' has type parameters and cannot be used as an attribute.
+
+ The syntax 'expr1[expr2]' is used for indexing. Consider adding a type annotation to enable indexing, or if calling a function add a space, e.g. 'expr1 [expr2]'.語法 'expr1[expr2]' 已用於編製索引。請考慮新增類型註釋來啟用編製索引,或是呼叫函式並新增空格,例如 'expr1 [expr2]'。
@@ -1767,6 +1807,41 @@
The value or member '{0}' has been marked 'inline' but is part of a recursive binding group. F# does not support recursive 'inline' values. Either remove the 'inline' modifier or refactor the recursion.
+
+ Spread field '{0}' shadows an explicitly declared field with the same name.
+ Spread field '{0}' shadows an explicitly declared field with the same name.
+
+
+
+ The source expression of a spread into a nominal record expression cannot be nullable.
+ The source expression of a spread into a nominal record expression cannot be nullable.
+
+
+
+ The source expression of a spread into a nominal record expression must have a nominal or anonymous record type.
+ The source expression of a spread into a nominal record expression must have a nominal or anonymous record type.
+
+
+
+ Spread expressions and 'with' cannot be used together in the same copy-and-update expression.
+ Spread expressions and 'with' cannot be used together in the same copy-and-update expression.
+
+
+
+ Spread field '{0}' from type '{1}' shadows an explicitly declared field with the same name.
+ Spread field '{0}' from type '{1}' shadows an explicitly declared field with the same name.
+
+
+
+ The source type of a spread into a record type definition cannot be nullable.
+ The source type of a spread into a record type definition cannot be nullable.
+
+
+
+ The source type of a spread into a record type definition must itself be a nominal or anonymous record type.
+ The source type of a spread into a record type definition must itself be a nominal or anonymous record type.
+
+ The 'let! ... and! ...' construct may only be used if the computation expression builder defines either a '{0}' method or appropriate 'MergeSources' and 'Bind' methods只有在計算運算式產生器定義 '{0}' 方法或正確的 'MergeSource' 和 'Bind' 方法時,才可使用 'let! ... and! ...' 建構
@@ -1852,6 +1927,11 @@
特徵不能指定選擇性、in、out、ParamArray、CallerInfo 或 Quote 引數
+
+ This type definition involves a cyclic reference through a spread.
+ This type definition involves a cyclic reference through a spread.
+
+ The type '{0}' does not support a nullness qualification.The type '{0}' does not support a nullness qualification.
@@ -6628,8 +6708,8 @@
- Unexpected syntax or possible incorrect indentation: this token is offside of context started at position {0}. Try indenting this further.\nTo continue using non-conforming indentation, pass the '--strict-indentation-' flag to the compiler, or set the language version to F# 7.
- 未預期的語法或可能不正確的縮排: 此權杖與在位置 {0} 啟動的內容不同步。請嘗試進一步縮排。\n若要繼續使用不符合的縮排,請傳遞 '--strict-indentation-' 旗標給編譯器,或將語言版本設定為 F# 7。
+ Unexpected syntax or possible incorrect indentation: this token is offside of context started at position {0}. Try indenting this further.
+ 未預期的語法或可能不正確的縮排: 此權杖與在位置 {0} 啟動的內容不同步。請嘗試進一步縮排。\n若要繼續使用不符合的縮排,請傳遞 '--strict-indentation-' 旗標給編譯器,或將語言版本設定為 F# 7。
diff --git a/src/Compiler/xlf/FSStrings.cs.xlf b/src/Compiler/xlf/FSStrings.cs.xlf
index 86f2f70699a..9c7f8dacff3 100644
--- a/src/Compiler/xlf/FSStrings.cs.xlf
+++ b/src/Compiler/xlf/FSStrings.cs.xlf
@@ -1,4 +1,4 @@
-
+
@@ -112,6 +112,11 @@
symbol '|' (directly before 'null')
+
+ symbol '...'
+ symbol '...'
+
+ symbol '..^'symbol ..^
diff --git a/src/Compiler/xlf/FSStrings.de.xlf b/src/Compiler/xlf/FSStrings.de.xlf
index 12e8860ae56..dcd1e5c6a30 100644
--- a/src/Compiler/xlf/FSStrings.de.xlf
+++ b/src/Compiler/xlf/FSStrings.de.xlf
@@ -1,4 +1,4 @@
-
+
@@ -112,6 +112,11 @@
symbol '|' (directly before 'null')
+
+ symbol '...'
+ symbol '...'
+
+ symbol '..^'Symbol "..^"
diff --git a/src/Compiler/xlf/FSStrings.es.xlf b/src/Compiler/xlf/FSStrings.es.xlf
index 317e1230228..a6b1d92f9b2 100644
--- a/src/Compiler/xlf/FSStrings.es.xlf
+++ b/src/Compiler/xlf/FSStrings.es.xlf
@@ -1,4 +1,4 @@
-
+
@@ -112,6 +112,11 @@
symbol '|' (directly before 'null')
+
+ symbol '...'
+ symbol '...'
+
+ symbol '..^'símbolo "..^"
diff --git a/src/Compiler/xlf/FSStrings.fr.xlf b/src/Compiler/xlf/FSStrings.fr.xlf
index c35bfe5ed08..db5381544a2 100644
--- a/src/Compiler/xlf/FSStrings.fr.xlf
+++ b/src/Compiler/xlf/FSStrings.fr.xlf
@@ -1,4 +1,4 @@
-
+
@@ -112,6 +112,11 @@
symbol '|' (directly before 'null')
+
+ symbol '...'
+ symbol '...'
+
+ symbol '..^'symbole '..^'
diff --git a/src/Compiler/xlf/FSStrings.it.xlf b/src/Compiler/xlf/FSStrings.it.xlf
index fcbe444c44e..902108cf645 100644
--- a/src/Compiler/xlf/FSStrings.it.xlf
+++ b/src/Compiler/xlf/FSStrings.it.xlf
@@ -1,4 +1,4 @@
-
+
@@ -112,6 +112,11 @@
symbol '|' (directly before 'null')
+
+ symbol '...'
+ symbol '...'
+
+ symbol '..^'simbolo '..^'
diff --git a/src/Compiler/xlf/FSStrings.ja.xlf b/src/Compiler/xlf/FSStrings.ja.xlf
index 75b5d835d55..97c3f25b53b 100644
--- a/src/Compiler/xlf/FSStrings.ja.xlf
+++ b/src/Compiler/xlf/FSStrings.ja.xlf
@@ -1,4 +1,4 @@
-
+
@@ -112,6 +112,11 @@
symbol '|' (directly before 'null')
+
+ symbol '...'
+ symbol '...'
+
+ symbol '..^'シンボル '..^'
diff --git a/src/Compiler/xlf/FSStrings.ko.xlf b/src/Compiler/xlf/FSStrings.ko.xlf
index b495c3a27c8..efd8b23b190 100644
--- a/src/Compiler/xlf/FSStrings.ko.xlf
+++ b/src/Compiler/xlf/FSStrings.ko.xlf
@@ -1,4 +1,4 @@
-
+
@@ -112,6 +112,11 @@
symbol '|' (directly before 'null')
+
+ symbol '...'
+ symbol '...'
+
+ symbol '..^'기호 '..^'
diff --git a/src/Compiler/xlf/FSStrings.pl.xlf b/src/Compiler/xlf/FSStrings.pl.xlf
index 7d5af0e89d1..8949f6d2643 100644
--- a/src/Compiler/xlf/FSStrings.pl.xlf
+++ b/src/Compiler/xlf/FSStrings.pl.xlf
@@ -1,4 +1,4 @@
-
+
@@ -112,6 +112,11 @@
symbol '|' (directly before 'null')
+
+ symbol '...'
+ symbol '...'
+
+ symbol '..^'symbol „..^”
diff --git a/src/Compiler/xlf/FSStrings.pt-BR.xlf b/src/Compiler/xlf/FSStrings.pt-BR.xlf
index 47f6b1d5cda..5e1b18362a9 100644
--- a/src/Compiler/xlf/FSStrings.pt-BR.xlf
+++ b/src/Compiler/xlf/FSStrings.pt-BR.xlf
@@ -1,4 +1,4 @@
-
+
@@ -112,6 +112,11 @@
symbol '|' (directly before 'null')
+
+ symbol '...'
+ symbol '...'
+
+ symbol '..^'símbolo '..^'
diff --git a/src/Compiler/xlf/FSStrings.ru.xlf b/src/Compiler/xlf/FSStrings.ru.xlf
index d420b749357..df53e00e608 100644
--- a/src/Compiler/xlf/FSStrings.ru.xlf
+++ b/src/Compiler/xlf/FSStrings.ru.xlf
@@ -1,4 +1,4 @@
-
+
@@ -112,6 +112,11 @@
symbol '|' (directly before 'null')
+
+ symbol '...'
+ symbol '...'
+
+ symbol '..^'символ "..^"
diff --git a/src/Compiler/xlf/FSStrings.tr.xlf b/src/Compiler/xlf/FSStrings.tr.xlf
index 43123521b37..ccbf93e7d51 100644
--- a/src/Compiler/xlf/FSStrings.tr.xlf
+++ b/src/Compiler/xlf/FSStrings.tr.xlf
@@ -1,4 +1,4 @@
-
+
@@ -112,6 +112,11 @@
symbol '|' (directly before 'null')
+
+ symbol '...'
+ symbol '...'
+
+ symbol '..^''..^' sembolü
diff --git a/src/Compiler/xlf/FSStrings.zh-Hans.xlf b/src/Compiler/xlf/FSStrings.zh-Hans.xlf
index 637d8d2b7a9..95cc39ed6f6 100644
--- a/src/Compiler/xlf/FSStrings.zh-Hans.xlf
+++ b/src/Compiler/xlf/FSStrings.zh-Hans.xlf
@@ -1,4 +1,4 @@
-
+
@@ -112,6 +112,11 @@
symbol '|' (directly before 'null')
+
+ symbol '...'
+ symbol '...'
+
+ symbol '..^'符号 "..^"
diff --git a/src/Compiler/xlf/FSStrings.zh-Hant.xlf b/src/Compiler/xlf/FSStrings.zh-Hant.xlf
index f4452359da8..06ed5826235 100644
--- a/src/Compiler/xlf/FSStrings.zh-Hant.xlf
+++ b/src/Compiler/xlf/FSStrings.zh-Hant.xlf
@@ -1,4 +1,4 @@
-
+
@@ -112,6 +112,11 @@
symbol '|' (directly before 'null')
+
+ symbol '...'
+ symbol '...'
+
+ symbol '..^'符號 '..^'
diff --git a/src/FSharp.Build/FSharp.Build.fsproj b/src/FSharp.Build/FSharp.Build.fsproj
index d7f814ce261..90912e95fe2 100644
--- a/src/FSharp.Build/FSharp.Build.fsproj
+++ b/src/FSharp.Build/FSharp.Build.fsproj
@@ -82,16 +82,13 @@
-
+
-
-
-
-
-
-
+
+
+
diff --git a/src/FSharp.Compiler.Interactive.Settings/FSharp.Compiler.Interactive.Settings.fsproj b/src/FSharp.Compiler.Interactive.Settings/FSharp.Compiler.Interactive.Settings.fsproj
index a8ecf73e065..0302ae845f5 100644
--- a/src/FSharp.Compiler.Interactive.Settings/FSharp.Compiler.Interactive.Settings.fsproj
+++ b/src/FSharp.Compiler.Interactive.Settings/FSharp.Compiler.Interactive.Settings.fsproj
@@ -45,7 +45,7 @@
-
+
diff --git a/src/FSharp.Compiler.LanguageServer/FSharp.Compiler.LanguageServer.fsproj b/src/FSharp.Compiler.LanguageServer/FSharp.Compiler.LanguageServer.fsproj
index e1b1f0b35f9..ffa91fc3cac 100644
--- a/src/FSharp.Compiler.LanguageServer/FSharp.Compiler.LanguageServer.fsproj
+++ b/src/FSharp.Compiler.LanguageServer/FSharp.Compiler.LanguageServer.fsproj
@@ -8,10 +8,12 @@
-
-
-
-
+
+
+
+
+
+
@@ -28,7 +30,7 @@
-
+
diff --git a/src/FSharp.DependencyManager.Nuget/FSharp.DependencyManager.Nuget.fsproj b/src/FSharp.DependencyManager.Nuget/FSharp.DependencyManager.Nuget.fsproj
index 500f3b32208..a24d5b0e5d9 100644
--- a/src/FSharp.DependencyManager.Nuget/FSharp.DependencyManager.Nuget.fsproj
+++ b/src/FSharp.DependencyManager.Nuget/FSharp.DependencyManager.Nuget.fsproj
@@ -51,13 +51,7 @@
-
-
-
-
-
-
-
+
diff --git a/src/FSharp.DependencyManager.Nuget/FSharp.DependencyManager.ProjectFile.fs b/src/FSharp.DependencyManager.Nuget/FSharp.DependencyManager.ProjectFile.fs
index 5386ea5a283..dfe128da71d 100644
--- a/src/FSharp.DependencyManager.Nuget/FSharp.DependencyManager.ProjectFile.fs
+++ b/src/FSharp.DependencyManager.Nuget/FSharp.DependencyManager.ProjectFile.fs
@@ -52,6 +52,7 @@ $(POUND_R)
$(RUNTIMEIDENTIFIER)falsetrue
+ falsetrue
@@ -114,6 +115,7 @@ $(PACKAGEREFERENCES)
<__Conflicts>@(__ConflictsList, ';');
+ <_CopyLocalNames>;@(__InteractiveReferencedAssembliesCopyLocal->'%(Filename)', ';');
@@ -138,6 +140,19 @@ $(PACKAGEREFERENCES)
%(__InteractiveReferencedAssembliesCopyLocal.NuGetPackageId)%(__InteractiveReferencedAssembliesCopyLocal.NuGetPackageVersion)
+
+
+
+ runtime
+ %(InteractiveResolvedFile.PackageRoot)content\%(InteractiveResolvedFile.NugetPackageId)$(SCRIPTEXTENSION)
diff --git a/src/FSharp.VisualStudio.Extension/FSharp.VisualStudio.Extension.csproj b/src/FSharp.VisualStudio.Extension/FSharp.VisualStudio.Extension.csproj
index 862decf5606..f2f8ab61ede 100644
--- a/src/FSharp.VisualStudio.Extension/FSharp.VisualStudio.Extension.csproj
+++ b/src/FSharp.VisualStudio.Extension/FSharp.VisualStudio.Extension.csproj
@@ -12,11 +12,12 @@
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/src/Microsoft.FSharp.Compiler/Microsoft.FSharp.Compiler.fsproj b/src/Microsoft.FSharp.Compiler/Microsoft.FSharp.Compiler.fsproj
index 36d7036a22c..0c6cddde221 100644
--- a/src/Microsoft.FSharp.Compiler/Microsoft.FSharp.Compiler.fsproj
+++ b/src/Microsoft.FSharp.Compiler/Microsoft.FSharp.Compiler.fsproj
@@ -12,7 +12,7 @@
-
+
@@ -101,4 +101,8 @@
DependsOnTargets="PackDependentProjectsCore;PackageReleaseDependentPackages">
+
+
+
+
diff --git a/src/fsc/fsc.targets b/src/fsc/fsc.targets
index c85dc1e66ab..f54cb4b32a9 100644
--- a/src/fsc/fsc.targets
+++ b/src/fsc/fsc.targets
@@ -43,7 +43,7 @@
-
+
@@ -53,14 +53,9 @@
-
-
-
-
-
-
-
-
+
+
+
diff --git a/src/fsi/fsi.targets b/src/fsi/fsi.targets
index cba9355e99f..b38960f7f0e 100644
--- a/src/fsi/fsi.targets
+++ b/src/fsi/fsi.targets
@@ -48,7 +48,7 @@
-
+
@@ -65,9 +65,9 @@
-
-
-
+
+
+
\ No newline at end of file
diff --git a/src/fsi/fsiProject/fsi.fsproj b/src/fsi/fsiProject/fsi.fsproj
index 1b955f9564e..58a300a0de9 100644
--- a/src/fsi/fsiProject/fsi.fsproj
+++ b/src/fsi/fsiProject/fsi.fsproj
@@ -10,7 +10,8 @@
$(FSharpNetCoreProductTargetFramework)
- $(EnablePublishReadyToRun)
+
+ false$(NETCoreSdkRuntimeIdentifier)
diff --git a/tests/AheadOfTime/Directory.Build.props b/tests/AheadOfTime/Directory.Build.props
index 6b0a85482a8..7c6ff208af6 100644
--- a/tests/AheadOfTime/Directory.Build.props
+++ b/tests/AheadOfTime/Directory.Build.props
@@ -4,6 +4,8 @@
+
+ false$(MSBuildThisFileDirectory)/../../artifacts/bin/fsc/Release/$(FSharpNetCoreProductTargetFramework)
diff --git a/tests/AheadOfTime/NativeAOT/NativeAOT_Test.fsproj b/tests/AheadOfTime/NativeAOT/NativeAOT_Test.fsproj
new file mode 100644
index 00000000000..1fa87907110
--- /dev/null
+++ b/tests/AheadOfTime/NativeAOT/NativeAOT_Test.fsproj
@@ -0,0 +1,36 @@
+
+
+
+ Exe
+ net9.0
+ preview
+ true
+
+
+
+ true
+ true
+ true
+ true
+ win-x64
+
+
+
+ $(LocalFSharpBuildBinPath)/FSharp.Build.dll
+ $(LocalFSharpBuildBinPath)/fsc.dll
+ $(LocalFSharpBuildBinPath)/fsc.dll
+ False
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/AheadOfTime/NativeAOT/Program.fs b/tests/AheadOfTime/NativeAOT/Program.fs
new file mode 100644
index 00000000000..dce1bbaf53e
--- /dev/null
+++ b/tests/AheadOfTime/NativeAOT/Program.fs
@@ -0,0 +1,34 @@
+module Program
+
+open System
+
+// Check a rendering against an expected string literal; a mismatch prints a "FAILED" line.
+let check (actual: string, expected: string) =
+ if actual <> expected then
+ Console.WriteLine $"FAILED: expected '{expected}' but got '{actual}'"
+
+let runChecks () =
+ let x = 42
+ let name = "world"
+ let pi = 3.14159
+ let initial = 'F'
+ check ($"answer = {x}", "answer = 42")
+ check ($"hello {name}", "hello world")
+ check ($"pi ~ {pi:F2}", "pi ~ 3.14")
+ check ($"padded:{x,6}", "padded: 42")
+ check ($"greeting %s{name}", "greeting world")
+ // Bare '%d'/'%i'/'%c'/'%M' specifiers lower to the same reflection-free path as a plain hole.
+ check ($"answer = %d{x}", "answer = 42")
+ check ($"initial = %c{initial}", "initial = F")
+
+ // The following use printf specifiers that still route through 'sprintf', so they would make the
+ // NativeAOT publish fail with IL2026/IL2070/IL3050.
+ // check ($"pi ~ %.2f{pi}", "pi ~ 3.14")
+ // check ($"value = %A{x}", "value = 42")
+
+[]
+let main _ =
+ runChecks ()
+ // Success sentinel; a failed check above printed a "FAILED" line first, so the output won't be just this.
+ Console.WriteLine "Finished"
+ 0
diff --git a/tests/AheadOfTime/NativeAOT/check.cmd b/tests/AheadOfTime/NativeAOT/check.cmd
new file mode 100644
index 00000000000..4eefff011c5
--- /dev/null
+++ b/tests/AheadOfTime/NativeAOT/check.cmd
@@ -0,0 +1,2 @@
+@echo off
+powershell -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0check.ps1""""
diff --git a/tests/AheadOfTime/NativeAOT/check.ps1 b/tests/AheadOfTime/NativeAOT/check.ps1
new file mode 100644
index 00000000000..dc69fb765df
--- /dev/null
+++ b/tests/AheadOfTime/NativeAOT/check.ps1
@@ -0,0 +1,37 @@
+# Publish the test project with NativeAOT and check that it runs.
+#
+# The point of this check is that the publish succeeds: a string-typed interpolated string
+# must lower to a reflection-free form (System.String.Concat), not the reflection-based
+# printf engine. If it regresses to printf, FSharp.Reflection becomes statically reachable,
+# NativeAOT analysis emits IL2026/IL2070/IL3050, TreatWarningsAsErrors turns them into errors,
+# and this publish fails.
+
+$ErrorActionPreference = "Stop"
+
+$root = "NativeAOT_Test"
+$tfm = "net9.0"
+
+$cwd = Get-Location
+Set-Location $PSScriptRoot
+
+dotnet publish -restore -c release -f:$tfm "$root.fsproj" -bl:"$PSScriptRoot/../../../artifacts/log/Release/AheadOfTime/NativeAOT/$root.binlog"
+if (-not ($LASTEXITCODE -eq 0)) {
+ Set-Location $cwd
+ Write-Error "NativeAOT publish failed with exit code $LASTEXITCODE" -ErrorAction Stop
+}
+
+$exe = Join-Path $PSScriptRoot "bin/release/$tfm/win-x64/publish/$root.exe"
+$output = (& $exe) -join "`n"
+$exitCode = $LASTEXITCODE
+Set-Location $cwd
+
+# The app prints a "FAILED" line per mismatch and "Finished" last, so its output is exactly "Finished" only if all checks passed.
+if (-not ($exitCode -eq 0)) {
+ Write-Error "NativeAOT app crashed with exit code $exitCode.`nOutput:`n$output" -ErrorAction Stop
+}
+
+if ($output.Trim() -ne "Finished") {
+ Write-Error "NativeAOT interpolation checks failed.`nOutput:`n$output" -ErrorAction Stop
+}
+
+Write-Host "NativeAOT interpolated-string test passed."
diff --git a/tests/AheadOfTime/Trimming/check.ps1 b/tests/AheadOfTime/Trimming/check.ps1
index 49cf96e31d3..406eefc616e 100644
--- a/tests/AheadOfTime/Trimming/check.ps1
+++ b/tests/AheadOfTime/Trimming/check.ps1
@@ -68,10 +68,10 @@ $allErrors += CheckTrim -root "SelfContained_Trimming_Test" -tfm "net9.0" -outpu
# Check net9.0 trimmed assemblies with static linked FSharpCore.
# Statically links FSharp.Compiler.Service; the size is stable now that its codegen is
# deterministic (#19928/#19929). Update if compiler/trimming output intentionally changes.
-$allErrors += CheckTrim -root "StaticLinkedFSharpCore_Trimming_Test" -tfm "net9.0" -outputfile "StaticLinkedFSharpCore_Trimming_Test.dll" -expected_len 9173504 -callerLineNumber 71
+$allErrors += CheckTrim -root "StaticLinkedFSharpCore_Trimming_Test" -tfm "net9.0" -outputfile "StaticLinkedFSharpCore_Trimming_Test.dll" -expected_len 9174016 -callerLineNumber 71
# Check net9.0 trimmed assemblies with F# metadata resources removed
-$allErrors += CheckTrim -root "FSharpMetadataResource_Trimming_Test" -tfm "net9.0" -outputfile "FSharpMetadataResource_Trimming_Test.dll" -expected_len 7612928 -callerLineNumber 74
+$allErrors += CheckTrim -root "FSharpMetadataResource_Trimming_Test" -tfm "net9.0" -outputfile "FSharpMetadataResource_Trimming_Test.dll" -expected_len 7613440 -callerLineNumber 74
# Report all errors and exit with failure if any occurred
if ($allErrors.Count -gt 0) {
diff --git a/tests/AheadOfTime/check.ps1 b/tests/AheadOfTime/check.ps1
index e8fd72b57e5..5c1de83b903 100644
--- a/tests/AheadOfTime/check.ps1
+++ b/tests/AheadOfTime/check.ps1
@@ -2,3 +2,4 @@ Write-Host "AheadOfTime: check1.ps1"
Equality\check.ps1
Trimming\check.ps1
+NativeAOT\check.ps1
diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props
index ccc7e44ffa3..38571805a89 100644
--- a/tests/Directory.Build.props
+++ b/tests/Directory.Build.props
@@ -5,23 +5,39 @@
trueportable
+
+ <_IsTestRunnerProject Condition="$(MSBuildProjectName.EndsWith('.Tests')) OR $(MSBuildProjectName.EndsWith('.ComponentTests')) OR $(MSBuildProjectName.EndsWith('.UnitTests'))">true
-
-
-
+
+
+
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
-
+ true
+
+ true
@@ -33,9 +49,8 @@
-
+ OutputType isn't available at props evaluation time, so this applies to all net472 test-runner projects. -->
+ x64
diff --git a/tests/EndToEndBuildTests/Directory.Build.props b/tests/EndToEndBuildTests/Directory.Build.props
index f97db4e1684..a40f84977bd 100644
--- a/tests/EndToEndBuildTests/Directory.Build.props
+++ b/tests/EndToEndBuildTests/Directory.Build.props
@@ -1,14 +1,15 @@
+
+ falsenet40LatestMajor3.2.2
- 3.2.22.0.28.0.0
- 17.14.1
+ 18.0.1
diff --git a/tests/FSharp.Build.UnitTests/FSharp.Build.UnitTests.fsproj b/tests/FSharp.Build.UnitTests/FSharp.Build.UnitTests.fsproj
index 2018b41cb92..0b489b6cc7c 100644
--- a/tests/FSharp.Build.UnitTests/FSharp.Build.UnitTests.fsproj
+++ b/tests/FSharp.Build.UnitTests/FSharp.Build.UnitTests.fsproj
@@ -25,13 +25,13 @@
-
+
-
-
-
+
+
+
diff --git a/tests/FSharp.Compiler.ComponentTests/Attributes/GenericAttributeAbbreviations.fs b/tests/FSharp.Compiler.ComponentTests/Attributes/GenericAttributeAbbreviations.fs
new file mode 100644
index 00000000000..4b303792554
--- /dev/null
+++ b/tests/FSharp.Compiler.ComponentTests/Attributes/GenericAttributeAbbreviations.fs
@@ -0,0 +1,98 @@
+namespace FSharp.Compiler.ComponentTests.Attributes
+
+open Xunit
+open FSharp.Test.Compiler
+
+module GenericAttributeAbbreviations =
+
+ // Repro from https://github.com/dotnet/fsharp/issues/7877.
+ // A type abbreviation of a generic attribute type must not crash with
+ // FS0193 "The lists had different lengths" - it must report FS3891.
+ []
+ let ``Type abbreviation of generic attribute reports FS3891 instead of crashing`` () =
+ Fsx """
+type A<'T>() = inherit System.Attribute()
+type B = A
+[] type C = class end
+"""
+ |> compile
+ |> shouldFail
+ |> withSingleDiagnostic (Error 3891, Line 4, Col 3, Line 4, Col 4, "Generic attribute types are not supported in F#. The type 'A' has type parameters and cannot be used as an attribute.")
+ |> ignore
+
+ []
+ [")>]
+ [")>]
+ [")>]
+ [>")>]
+ let ``Generic attribute abbreviation variants all report FS3891`` (abbrev: string) =
+ Fsx (sprintf """
+type A<'T>() = inherit System.Attribute()
+%s
+[] type C = class end
+""" abbrev)
+ |> compile
+ |> shouldFail
+ |> withErrorCode 3891
+ |> ignore
+
+ []
+ let ``Two-parameter generic attribute abbreviation reports FS3891`` () =
+ Fsx """
+type A2<'T, 'U>() = inherit System.Attribute()
+type B = A2
+[] type C = class end
+"""
+ |> compile
+ |> shouldFail
+ |> withErrorCode 3891
+ |> ignore
+
+ []
+ let ``Chained abbreviation through a generic attribute reports FS3891`` () =
+ Fsx """
+type A<'T>() = inherit System.Attribute()
+type B = A
+type C2 = B
+[] type D = class end
+"""
+ |> compile
+ |> shouldFail
+ |> withErrorCode 3891
+ |> ignore
+
+ // Non-regression: a non-generic attribute abbreviation must still compile.
+ []
+ let ``Non-generic attribute abbreviation is unchanged`` () =
+ Fsx """
+type A() = inherit System.Attribute()
+type B = A
+[] type C = class end
+"""
+ |> compile
+ |> shouldSucceed
+ |> ignore
+
+ // Non-regression: built-in attribute abbreviated and used should compile.
+ []
+ let ``Abbreviation of non-generic System attribute compiles`` () =
+ Fsx """
+type MyObsolete = System.ObsoleteAttribute
+[]
+let foo () = ()
+"""
+ |> compile
+ |> shouldSucceed
+ |> ignore
+
+ // Non-regression: the direct `[>]` syntax is rejected by the parser,
+ // not by the new check. Behavior here must not change.
+ []
+ let ``Direct generic attribute syntax remains a parse-level rejection`` () =
+ Fsx """
+type A<'T>() = inherit System.Attribute()
+[>] type C = class end
+"""
+ |> compile
+ |> shouldFail
+ |> ignore
diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerDirectives/Line.fs b/tests/FSharp.Compiler.ComponentTests/CompilerDirectives/Line.fs
index 51a171b3ac4..6eef92698cd 100644
--- a/tests/FSharp.Compiler.ComponentTests/CompilerDirectives/Line.fs
+++ b/tests/FSharp.Compiler.ComponentTests/CompilerDirectives/Line.fs
@@ -135,7 +135,7 @@ printfn ""
PathMap.empty,
true
)
- let lexbuf = StringAsLexbuf(true, langVersion, None, sourceText)
+ let lexbuf = StringAsLexbuf(true, langVersion, sourceText)
resetLexbufPos "testt.fs" lexbuf
let tokenizer _ =
let t = Lexer.token lexargs true lexbuf
diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerOptions/Fsc/UncoveredOptions.fs b/tests/FSharp.Compiler.ComponentTests/CompilerOptions/Fsc/UncoveredOptions.fs
index dc0c6e0762f..ef40f3b8159 100644
--- a/tests/FSharp.Compiler.ComponentTests/CompilerOptions/Fsc/UncoveredOptions.fs
+++ b/tests/FSharp.Compiler.ComponentTests/CompilerOptions/Fsc/UncoveredOptions.fs
@@ -19,8 +19,6 @@ module UncoveredOptions =
[]
[]
[]
- []
- []
[]
[]
[]
diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerOptions/fsc/misc/compiler_help_output.bsl b/tests/FSharp.Compiler.ComponentTests/CompilerOptions/fsc/misc/compiler_help_output.bsl
index 56df6419a54..fb9b669e05c 100644
--- a/tests/FSharp.Compiler.ComponentTests/CompilerOptions/fsc/misc/compiler_help_output.bsl
+++ b/tests/FSharp.Compiler.ComponentTests/CompilerOptions/fsc/misc/compiler_help_output.bsl
@@ -83,7 +83,6 @@ Copyright (c) Microsoft Corporation. All Rights Reserved.
--disableLanguageFeature: Disable a specific language feature by name.
--checked[+|-] Generate overflow checks (off by default)
--define: Define conditional compilation symbols (Short form: -d)
---strict-indentation[+|-] Override indentation rules implied by the language version (off by default)
--always-inline[+|-] Always inline 'inline' functions
diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerOptions/fsc/reflectionfree.fs b/tests/FSharp.Compiler.ComponentTests/CompilerOptions/fsc/reflectionfree.fs
index 65b96d7d9c8..da96fa9bb96 100644
--- a/tests/FSharp.Compiler.ComponentTests/CompilerOptions/fsc/reflectionfree.fs
+++ b/tests/FSharp.Compiler.ComponentTests/CompilerOptions/fsc/reflectionfree.fs
@@ -35,15 +35,296 @@ let someCode =
"""
[]
-let ``Records and DUs don't have generated ToString`` () =
+let ``Classes don't have a generated ToString`` () =
someCode
|> withOptions [ "--reflectionfree" ]
|> compileExeAndRun
|> shouldSucceed
- |> withStdOutContains "Thing says: Test+MyRecord"
- |> withStdOutContains "Thing says: Test+MyUnion+B"
|> withStdOutContains "Thing says: Test+MyClass"
+[]
+let ``Records get a generated single-line ToString`` () =
+ FSharp """
+module Test
+type Point = { X: int; Y: int }
+type Nested = { P: Point; S: string }
+
+[]
+let main _ =
+ { X = 1; Y = 2 } |> string |> System.Console.WriteLine
+ { P = { X = 1; Y = 2 }; S = null } |> string |> System.Console.WriteLine // nested record + null field
+ 0
+ """
+ |> asExe
+ |> withOptions [ "--reflectionfree" ]
+ |> compileExeAndRun
+ |> shouldSucceed
+ |> withStdOutContains "{ X = 1; Y = 2 }"
+ |> withStdOutContains "{ P = { X = 1; Y = 2 }; S = null }"
+
+[]
+let ``Unions have a generated ToString that matches on the case`` () =
+ someCode
+ |> withOptions [ "--reflectionfree" ]
+ |> compileExeAndRun
+ |> shouldSucceed
+ |> withStdOutContains "Thing says: B(foo)"
+
+[]
+let ``Generic unions get a correct generated ToString`` () =
+ FSharp """
+module Test
+type Box<'T> =
+ | Box of 'T
+ | Empty
+type Single<'T> = | Just of 'T
+
+[]
+let main _ =
+ Box 42 |> string |> System.Console.WriteLine
+ Box (Box 7) |> string |> System.Console.WriteLine // nested generic
+ (Empty: Box) |> string |> System.Console.WriteLine
+ Just 5 |> string |> System.Console.WriteLine // single-case generic union
+ 0
+ """
+ |> asExe
+ |> withOptions [ "--reflectionfree" ]
+ |> compileExeAndRun
+ |> shouldSucceed
+ |> withStdOutContains "Box(42)"
+ |> withStdOutContains "Box(Box(7))"
+ |> withStdOutContains "Empty"
+ |> withStdOutContains "Just(5)"
+
+[]
+let ``Generated ToString renders a field the same way option does`` () =
+ FSharp """
+module Test
+type Wrapper = | Wrap of string
+
+[]
+let main _ =
+ let value: string = null
+ // A union field should render its content the same way option does. Compare the two directly rather
+ // than asserting a fixed rendering. "Wrap" and "Some" are both 4 chars, so dropping them leaves the
+ // field rendering to compare.
+ let fromUnion = (Wrap value |> string).Substring 4
+ let fromOption = ((Some value).ToString()).Substring 4
+ if fromUnion = fromOption then System.Console.WriteLine "fields-render-alike"
+ else System.Console.WriteLine("DIFFER: " + fromUnion + " vs " + fromOption)
+ 0
+ """
+ |> asExe
+ |> withOptions [ "--reflectionfree" ]
+ |> compileExeAndRun
+ |> shouldSucceed
+ |> withStdOutContains "fields-render-alike"
+
+[]
+let ``A hand-written ToString override is kept, not replaced by the generated one`` () =
+ FSharp """
+module Test
+type MyDU =
+ | A of int
+ override _.ToString() = "custom-du"
+
+type MyRecord =
+ { X: int }
+ override _.ToString() = "custom-record"
+
+[]
+let main _ =
+ A 1 |> string |> System.Console.WriteLine
+ { X = 1 } |> string |> System.Console.WriteLine
+ 0
+ """
+ |> asExe
+ |> withOptions [ "--reflectionfree" ]
+ |> compileExeAndRun
+ |> shouldSucceed
+ |> withStdOutContains "custom-du"
+ |> withStdOutContains "custom-record"
+
+[]
+let ``Union field shapes: multiple fields versus a single tuple field`` () =
+ FSharp """
+module Test
+type TwoFields = | Two of int * int
+type OneTupleField = | OneTup of (int * int)
+type NamedFields = | Named of x: int * y: int
+
+[]
+let main _ =
+ Two (1, 2) |> string |> System.Console.WriteLine
+ OneTup (1, 2) |> string |> System.Console.WriteLine // a single tuple field keeps its own parens
+ Named (1, 2) |> string |> System.Console.WriteLine // named fields render positionally, names are not shown
+ 0
+ """
+ |> asExe
+ |> withOptions [ "--reflectionfree" ]
+ |> compileExeAndRun
+ |> shouldSucceed
+ |> withStdOutContains "Two(1, 2)"
+ |> withStdOutContains "OneTup((1, 2))"
+ |> withStdOutContains "Named(1, 2)"
+
+[]
+let ``Explicit field names do not change the rendering`` () =
+ FSharp """
+module Test
+type Labelled = | WithNames of first: int * second: string
+type Plain = | WithoutNames of int * string
+
+[]
+let main _ =
+ WithNames (1, "a") |> string |> System.Console.WriteLine
+ WithoutNames (1, "a") |> string |> System.Console.WriteLine // unnamed fields render the same way as named ones
+ 0
+ """
+ |> asExe
+ |> withOptions [ "--reflectionfree" ]
+ |> compileExeAndRun
+ |> shouldSucceed
+ |> withStdOutContains "WithNames(1, a)"
+ |> withStdOutContains "WithoutNames(1, a)"
+
+[]
+let ``Backtick-quoted names render without their backticks`` () =
+ FSharp """
+module Test
+type Quoted = | ``My Case`` of int
+type QuotedField = { ``My Field``: int }
+
+[]
+let main _ =
+ ``My Case`` 5 |> string |> System.Console.WriteLine
+ { ``My Field`` = 5 } |> string |> System.Console.WriteLine
+ 0
+ """
+ |> asExe
+ |> withOptions [ "--reflectionfree" ]
+ |> compileExeAndRun
+ |> shouldSucceed
+ |> withStdOutContains "My Case(5)"
+ |> withStdOutContains "{ My Field = 5 }"
+
+[]
+let ``Struct unions and struct records get a generated ToString`` () =
+ FSharp """
+module Test
+[] type StructUnion = | SA of a: int
+[] type StructRecord = { SX: int; SY: int }
+
+[]
+let main _ =
+ SA 7 |> string |> System.Console.WriteLine
+ { SX = 1; SY = 2 } |> string |> System.Console.WriteLine
+ 0
+ """
+ |> asExe
+ |> withOptions [ "--reflectionfree" ]
+ |> compileExeAndRun
+ |> shouldSucceed
+ |> withStdOutContains "SA(7)"
+ |> withStdOutContains "{ SX = 1; SY = 2 }"
+
+[]
+let ``Anonymous records get a generated single-line ToString`` () =
+ FSharp """
+module Test
+[]
+let main _ =
+ {| A = 1; B = "hi" |} |> string |> System.Console.WriteLine
+ (struct {| A = 1; B = "hi" |}) |> string |> System.Console.WriteLine // a struct anonymous record renders identically
+ 0
+ """
+ |> asExe
+ |> withOptions [ "--reflectionfree" ]
+ |> compileExeAndRun
+ |> shouldSucceed
+ |> withStdOutContains "{| A = 1; B = hi |}"
+
+[]
+let ``An empty anonymous record renders with a single inner space`` () =
+ FSharp """
+module Test
+[]
+let main _ =
+ System.Console.WriteLine("[" + string {| |} + "]") // the empty braces keep a single space, not a doubled one
+ 0
+ """
+ |> asExe
+ |> withOptions [ "--reflectionfree" ]
+ |> compileExeAndRun
+ |> shouldSucceed
+ |> withStdOutContains "[{| |}]"
+
+[]
+let ``Recursively defined types render when the data is finite`` () =
+ FSharp """
+module Test
+type Tree = | Leaf | Node of Tree * int * Tree
+type TreeNode = { Value: int; Parent: TreeNode option } // an upward-only parent pointer stays finite
+
+[]
+let main _ =
+ Node (Node (Leaf, 1, Leaf), 2, Leaf) |> string |> System.Console.WriteLine
+ let root = { Value = 0; Parent = None }
+ { Value = 1; Parent = Some root } |> string |> System.Console.WriteLine
+ 0
+ """
+ |> asExe
+ |> withOptions [ "--reflectionfree" ]
+ |> compileExeAndRun
+ |> shouldSucceed
+ |> withStdOutContains "Node(Node(Leaf, 1, Leaf), 2, Leaf)"
+ |> withStdOutContains "{ Value = 1; Parent = Some({ Value = 0; Parent = null }) }"
+
+[]
+let ``Deeply nested data fails the generated ToString with a catchable exception, not a hard overflow`` () =
+ FSharp """
+module Test
+type Chain = | End | Link of int * Chain
+
+[]
+let main _ =
+ let mutable c = End
+ for i in 1 .. 1_000_000 do c <- Link(i, c)
+ try
+ c.ToString() |> ignore
+ System.Console.WriteLine "rendered"
+ with :? System.InsufficientExecutionStackException ->
+ System.Console.WriteLine "caught"
+ 0
+ """
+ |> asExe
+ |> withOptions [ "--reflectionfree" ]
+ |> compileExeAndRun
+ |> shouldSucceed
+ |> withStdOutContains "caught"
+
+[]
+let ``Deeply nested anonymous records fail the generated ToString with a catchable exception, not a hard overflow`` () =
+ FSharp """
+module Test
+
+[]
+let main _ =
+ let mutable o: obj = box 0
+ for _ in 1 .. 1_000_000 do o <- box {| Next = o |}
+ try
+ o.ToString() |> ignore
+ System.Console.WriteLine "rendered"
+ with :? System.InsufficientExecutionStackException ->
+ System.Console.WriteLine "caught"
+ 0
+ """
+ |> asExe
+ |> withOptions [ "--reflectionfree" ]
+ |> compileExeAndRun
+ |> shouldSucceed
+ |> withStdOutContains "caught"
+
[]
let ``No debug display attribute`` () =
someCode
diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerService/Caches.fs b/tests/FSharp.Compiler.ComponentTests/CompilerService/Caches.fs
index b7aac72a93b..c00f1af81ba 100644
--- a/tests/FSharp.Compiler.ComponentTests/CompilerService/Caches.fs
+++ b/tests/FSharp.Compiler.ComponentTests/CompilerService/Caches.fs
@@ -16,6 +16,12 @@ let shouldNeverTimeout = 200_000
let defaultStructural() = CacheOptions.getDefault HashIdentity.Structural
+// Metrics assertions below read absolute per-name totals via CacheMetrics.getTotalsByName. Those totals
+// are aggregated process-globally while a CacheMetrics.ListenToAll() listener is running. This works
+// because each test uses a unique cache name and this module is the only ListenToAll caller in the
+// assembly, so nothing else increments those names. A second concurrently-active listener would
+// double-count every measurement, so keep it that way.
+
[]
let ``Create and dispose many`` () =
let caches =
@@ -28,8 +34,8 @@ let ``Create and dispose many`` () =
[]
let ``Basic add and retrieve`` () =
let name = "Basic_add_and_retrieve"
+ use _ = CacheMetrics.ListenToAll()
use cache = new Cache(defaultStructural(), name = name)
- use metricsListener = cache.CreateMetricsListener()
cache.TryAdd("key1", 1) |> shouldBeTrue
cache.TryAdd("key2", 2) |> shouldBeTrue
@@ -45,14 +51,14 @@ let ``Basic add and retrieve`` () =
cache.TryGetValue("key3", &value) |> shouldBeFalse
// Metrics assertions
- let totals = metricsListener.GetTotals()
+ let totals = CacheMetrics.getTotalsByName name
totals.["adds"] |> shouldEqual 2L
[]
let ``Eviction of least recently used`` () =
let name = "Eviction_of_least_recently_used"
+ use _ = CacheMetrics.ListenToAll()
use cache = new Cache({ defaultStructural() with TotalCapacity = 2; HeadroomPercentage = 0 }, name = name)
- use metricsListener = cache.CreateMetricsListener()
cache.TryAdd("key1", 1) |> shouldBeTrue
cache.TryAdd("key2", 2) |> shouldBeTrue
@@ -76,7 +82,7 @@ let ``Eviction of least recently used`` () =
value |> shouldEqual 3
// Metrics assertions
- let totals = metricsListener.GetTotals()
+ let totals = CacheMetrics.getTotalsByName name
totals.["adds"] |> shouldEqual 3L
[]
@@ -85,14 +91,14 @@ let ``Stress test evictions`` () =
let iterations = 10_000
let name = "Stress test evictions"
+ use _ = CacheMetrics.ListenToAll()
use cache = new Cache({ defaultStructural() with TotalCapacity = cacheSize; HeadroomPercentage = 0 }, name = name)
- use metricsListener = cache.CreateMetricsListener()
let evictionsCompleted = new TaskCompletionSource()
let expectedEvictions = iterations - cacheSize
cache.Evicted.Add <| fun () ->
- if metricsListener.GetTotals().["evictions"] = expectedEvictions then
+ if (CacheMetrics.getTotalsByName name).["evictions"] = expectedEvictions then
evictionsCompleted.SetResult()
cache.EvictionFailed.Add <| fun _ ->
@@ -114,13 +120,14 @@ let ``Stress test evictions`` () =
value |> shouldEqual iterations
// Metrics assertions
- let totals = metricsListener.GetTotals()
+ let totals = CacheMetrics.getTotalsByName name
totals.["adds"] |> shouldEqual (int64 iterations)
[]
let ``Metrics can be retrieved`` () =
- use cache = new Cache({ defaultStructural() with TotalCapacity = 2; HeadroomPercentage = 0 }, name = "test_metrics")
- use metricsListener = cache.CreateMetricsListener()
+ let name = "test_metrics"
+ use _ = CacheMetrics.ListenToAll()
+ use cache = new Cache({ defaultStructural() with TotalCapacity = 2; HeadroomPercentage = 0 }, name = name)
cache.TryAdd("key1", 1) |> shouldBeTrue
cache.TryAdd("key2", 2) |> shouldBeTrue
@@ -135,17 +142,17 @@ let ``Metrics can be retrieved`` () =
cache.TryAdd("key3", 3) |> shouldBeTrue
evictionCompleted.Task.Wait shouldNeverTimeout |> shouldBeTrue
- let totals = metricsListener.GetTotals()
+ let totals = CacheMetrics.getTotalsByName name
- metricsListener.Ratio |> shouldEqual 1.0
+ CacheMetrics.getRatioByName name |> shouldEqual 1.0
totals.["evictions"] |> shouldEqual 1L
totals.["adds"] |> shouldEqual 3L
[]
let ``GetOrAdd basic usage`` () =
let cacheName = "GetOrAdd_basic_usage"
+ use _ = CacheMetrics.ListenToAll()
use cache = new Cache(defaultStructural(), name = cacheName)
- use metricsListener = cache.CreateMetricsListener()
let mutable factoryCalls = 0
let factory k = factoryCalls <- factoryCalls + 1; String.length k
let v1 = cache.GetOrAdd("abc", factory)
@@ -157,17 +164,17 @@ let ``GetOrAdd basic usage`` () =
v3 |> shouldEqual 4
factoryCalls |> shouldEqual 2
// Metrics assertions
- let totals = metricsListener.GetTotals()
+ let totals = CacheMetrics.getTotalsByName cacheName
totals.["hits"] |> shouldEqual 1L
totals.["misses"] |> shouldEqual 2L
- metricsListener.Ratio |> shouldEqual (1.0/3.0)
+ CacheMetrics.getRatioByName cacheName |> shouldEqual (1.0/3.0)
totals.["adds"] |> shouldEqual 2L
[]
let ``AddOrUpdate basic usage`` () =
let cacheName = "AddOrUpdate_basic_usage"
+ use _ = CacheMetrics.ListenToAll()
use cache = new Cache(defaultStructural(), name = cacheName)
- use metricsListener = cache.CreateMetricsListener()
cache.AddOrUpdate("x", 1)
let mutable value = 0
cache.TryGetValue("x", &value) |> shouldBeTrue
@@ -179,10 +186,10 @@ let ``AddOrUpdate basic usage`` () =
cache.TryGetValue("y", &value) |> shouldBeTrue
value |> shouldEqual 99
// Metrics assertions
- let totals = metricsListener.GetTotals()
+ let totals = CacheMetrics.getTotalsByName cacheName
totals.["hits"] |> shouldEqual 3L // 3 cache hits
totals.["misses"] |> shouldEqual 0L // 0 cache misses
- metricsListener.Ratio |> shouldEqual 1.0
+ CacheMetrics.getRatioByName cacheName |> shouldEqual 1.0
totals.["adds"] |> shouldEqual 2L // "x" and "y" added
totals.["updates"] |> shouldEqual 1L // "x" updated
@@ -191,8 +198,8 @@ type BoxedKey = BoxedKey of int * int
[]
let ``GetOrAdd with reference identity`` () =
let cacheName = "GetOrAdd_with_Reference"
+ use _ = CacheMetrics.ListenToAll()
use cache = new Cache(CacheOptions.getReferenceIdentity(), cacheName)
- use metricsListener = cache.CreateMetricsListener()
let t1 = BoxedKey (1, 2)
let t2 = BoxedKey (1, 2)
let t3 = BoxedKey (1, 2)
@@ -219,17 +226,17 @@ let ``GetOrAdd with reference identity`` () =
v1'' |> shouldEqual v1'
v2'' |> shouldEqual v2'
// Metrics assertions
- let totals = metricsListener.GetTotals()
+ let totals = CacheMetrics.getTotalsByName cacheName
totals.["hits"] |> shouldEqual 4L
totals.["misses"] |> shouldEqual 3L
- metricsListener.Ratio |> shouldEqual (4.0 / 7.0)
+ CacheMetrics.getRatioByName cacheName |> shouldEqual (4.0 / 7.0)
totals.["adds"] |> shouldEqual 2L
[]
let ``AddOrUpdate with reference identity`` () =
let cacheName = "AddOrUpdate_with_Reference"
+ use _ = CacheMetrics.ListenToAll()
use cache = new Cache(CacheOptions.getReferenceIdentity(), name = cacheName)
- use metricsListener = cache.CreateMetricsListener()
let t1 = box (3, 4)
let t2 = box (3, 4)
cache.AddOrUpdate(t1, 7)
@@ -248,9 +255,9 @@ let ``AddOrUpdate with reference identity`` () =
cache.TryGetValue(t1, &value1Updated) |> shouldBeTrue
value1Updated |> shouldEqual 9
// Metrics assertions
- let totals = metricsListener.GetTotals()
+ let totals = CacheMetrics.getTotalsByName cacheName
totals.["hits"] |> shouldEqual 3L // 3 cache hits
totals.["misses"] |> shouldEqual 0L // 0 cache misses
- metricsListener.Ratio |> shouldEqual 1.0
+ CacheMetrics.getRatioByName cacheName |> shouldEqual 1.0
totals.["adds"] |> shouldEqual 2L // t1 and t2 added
totals.["updates"] |> shouldEqual 1L // t1 updated once
diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerService/EncMethodDebugInformationTests.fs b/tests/FSharp.Compiler.ComponentTests/CompilerService/EncMethodDebugInformationTests.fs
new file mode 100644
index 00000000000..9f933aa7863
--- /dev/null
+++ b/tests/FSharp.Compiler.ComponentTests/CompilerService/EncMethodDebugInformationTests.fs
@@ -0,0 +1,659 @@
+// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
+
+module CompilerService.EncMethodDebugInformationTests
+
+open System
+open System.Collections.Immutable
+open System.IO
+open System.Reflection
+open System.Reflection.Metadata
+open System.Reflection.Metadata.Ecma335
+open System.Reflection.PortableExecutable
+open Xunit
+
+open Internal.Utilities
+open FSharp.Compiler.AbstractIL.IL
+open FSharp.Compiler.AbstractIL.ILBinaryWriter
+open FSharp.Compiler.AbstractIL.ILPdbWriter
+open FSharp.Compiler.AbstractIL.EncMethodDebugInformation
+
+// -----------------------------------------------------------------------
+// Round-trip properties (pure codec)
+// -----------------------------------------------------------------------
+
+[]
+let ``Empty maps serialize to empty blobs and deserialize to the empty record`` () =
+ let info = EncMethodDebugInformation.Empty
+
+ Assert.Empty(serializeLocalSlots info)
+ Assert.Empty(serializeLambdaMap info)
+ Assert.Empty(serializeStateMachineStates info)
+
+ let decoded = deserialize Array.empty Array.empty Array.empty
+ Assert.Equal(EncMethodDebugInformation.Empty, decoded)
+
+ // Null blobs (absent CDI rows) behave like empty ones.
+ let decodedNull = deserialize null null null
+ Assert.Equal(EncMethodDebugInformation.Empty, decodedNull)
+
+[]
+let ``Lambda map with a single closure round-trips`` () =
+ let info =
+ { EncMethodDebugInformation.Empty with
+ MethodOrdinal = 0
+ Closures = [ { SyntaxOffset = 3 } ] }
+
+ let blob = serializeLambdaMap info
+ let methodOrdinal, closures, lambdas = deserializeLambdaMap blob
+
+ Assert.Equal(0, methodOrdinal)
+ Assert.Equal([ { SyntaxOffset = 3 } ], closures)
+ Assert.Empty lambdas
+
+[]
+let ``Lambda map with several lambdas and negative-baseline offsets round-trips`` () =
+ // Out-of-order and negative offsets exercise the syntax-offset-baseline record;
+ // closure ordinals cover in-range, static (-1) and this-only (-2) lambdas.
+ let closures = [ { SyntaxOffset = 12 }; { SyntaxOffset = -7 }; { SyntaxOffset = 3 } ]
+
+ let lambdas =
+ [ { SyntaxOffset = 30; ClosureOrdinal = 1 }
+ { SyntaxOffset = -7; ClosureOrdinal = StaticClosureOrdinal }
+ { SyntaxOffset = 0; ClosureOrdinal = ThisOnlyClosureOrdinal }
+ { SyntaxOffset = 5; ClosureOrdinal = 2 } ]
+
+ let info =
+ { EncMethodDebugInformation.Empty with
+ MethodOrdinal = 5
+ Closures = closures
+ Lambdas = lambdas }
+
+ let blob = serializeLambdaMap info
+ let methodOrdinal, decodedClosures, decodedLambdas = deserializeLambdaMap blob
+
+ Assert.Equal(5, methodOrdinal)
+ Assert.Equal(closures, decodedClosures)
+ Assert.Equal(lambdas, decodedLambdas)
+
+[]
+let ``Lambda map golden bytes match the Roslyn encoding`` () =
+ // methodOrdinal 0 -> compressed(1); baseline -1 -> compressed(1); one closure at
+ // offset 0 -> compressed(1); lambda at offset 5 -> compressed(6) with closure
+ // ordinal 0 -> compressed(0 - (-2)) = compressed(2).
+ let info =
+ { EncMethodDebugInformation.Empty with
+ MethodOrdinal = 0
+ Closures = [ { SyntaxOffset = 0 } ]
+ Lambdas = [ { SyntaxOffset = 5; ClosureOrdinal = 0 } ] }
+
+ Assert.Equal([| 0x01uy; 0x01uy; 0x01uy; 0x01uy; 0x06uy; 0x02uy |], serializeLambdaMap info)
+
+[]
+let ``Lambda map rejects closure ordinals outside the valid range`` () =
+ let mk ordinal =
+ { EncMethodDebugInformation.Empty with
+ MethodOrdinal = 0
+ Closures = [ { SyntaxOffset = 0 } ]
+ Lambdas = [ { SyntaxOffset = 1; ClosureOrdinal = ordinal } ] }
+
+ Assert.Throws(fun () -> serializeLambdaMap (mk 1) |> ignore) |> ignore
+ Assert.Throws(fun () -> serializeLambdaMap (mk -3) |> ignore) |> ignore
+
+[]
+let ``Slot map with temps, ordinal-flagged slots and negative offsets round-trips`` () =
+ let slots =
+ [ EncLocalSlotInfo.Temp
+ EncLocalSlotInfo.Slot(0, 10, 0)
+ EncLocalSlotInfo.Slot(MaxSerializableLocalKind, -42, 3)
+ EncLocalSlotInfo.Temp
+ EncLocalSlotInfo.Slot(7, 0, 1) ]
+
+ let info =
+ { EncMethodDebugInformation.Empty with
+ LocalSlots = slots }
+
+ let blob = serializeLocalSlots info
+ Assert.Equal(slots, deserializeLocalSlots blob)
+
+ // The baseline record must be present (an offset below -1 exists) and must be
+ // the Roslyn marker byte 0xFF followed by compressed(42).
+ Assert.Equal(0xFFuy, blob[0])
+
+[]
+let ``Slot map golden bytes match the Roslyn encoding`` () =
+ // No offset below -1 -> no baseline record (implicit baseline -1).
+ // Temp -> 0x00.
+ // Slot(kind 0, offset 0, ordinal 0) -> byte 0x01 (kind+1), compressed(0 - (-1)) = 0x01.
+ // Slot(kind 1, offset 2, ordinal 3) -> byte 0x82 (kind+1, bit 7 = has ordinal),
+ // compressed(3), compressed(3).
+ let info =
+ { EncMethodDebugInformation.Empty with
+ LocalSlots =
+ [ EncLocalSlotInfo.Temp
+ EncLocalSlotInfo.Slot(0, 0, 0)
+ EncLocalSlotInfo.Slot(1, 2, 3) ] }
+
+ Assert.Equal([| 0x00uy; 0x01uy; 0x01uy; 0x82uy; 0x03uy; 0x03uy |], serializeLocalSlots info)
+
+[]
+let ``Slot map rejects kinds outside the serializable range`` () =
+ let mk kind =
+ { EncMethodDebugInformation.Empty with
+ LocalSlots = [ EncLocalSlotInfo.Slot(kind, 0, 0) ] }
+
+ Assert.Throws(fun () -> serializeLocalSlots (mk -1) |> ignore) |> ignore
+
+ Assert.Throws(fun () -> serializeLocalSlots (mk (MaxSerializableLocalKind + 1)) |> ignore)
+ |> ignore
+
+[]
+let ``State machine map with negative state numbers round-trips ordered by offset`` () =
+ // Input deliberately unsorted; the writer orders entries by syntax offset
+ // (stably, so the two entries sharing offset 20 keep their relative order).
+ let states =
+ [ { StateNumber = -4; SyntaxOffset = 20 }
+ { StateNumber = 0; SyntaxOffset = -5 }
+ { StateNumber = 3; SyntaxOffset = 20 }
+ { StateNumber = 1; SyntaxOffset = 7 } ]
+
+ let info =
+ { EncMethodDebugInformation.Empty with
+ StateMachineStates = states }
+
+ let expected =
+ [ { StateNumber = 0; SyntaxOffset = -5 }
+ { StateNumber = 1; SyntaxOffset = 7 }
+ { StateNumber = -4; SyntaxOffset = 20 }
+ { StateNumber = 3; SyntaxOffset = 20 } ]
+
+ let blob = serializeStateMachineStates info
+ Assert.Equal(expected, deserializeStateMachineStates blob)
+
+[]
+let ``Full record round-trips through the three blobs`` () =
+ let info =
+ { MethodOrdinal = 2
+ LocalSlots = [ EncLocalSlotInfo.Slot(0, 4, 0); EncLocalSlotInfo.Temp ]
+ Closures = [ { SyntaxOffset = 0 } ]
+ Lambdas = [ { SyntaxOffset = 9; ClosureOrdinal = 0 } ]
+ StateMachineStates = [ { StateNumber = 0; SyntaxOffset = 9 } ] }
+
+ let decoded =
+ deserialize (serializeLocalSlots info) (serializeLambdaMap info) (serializeStateMachineStates info)
+
+ Assert.Equal(info, decoded)
+
+// -----------------------------------------------------------------------
+// Occurrence-key packing
+// -----------------------------------------------------------------------
+
+[]
+let ``Occurrence keys pack and unpack ordinal chains`` () =
+ // Depth 1: the key is the ordinal itself.
+ Assert.Equal(Some 0, tryEncodeOccurrenceKey [ 0 ])
+ Assert.Equal(Some 5, tryEncodeOccurrenceKey [ 5 ])
+ Assert.Equal(Some 0xFFFF, tryEncodeOccurrenceKey [ 0xFFFF ])
+ Assert.Equal([ 5 ], decodeOccurrenceKey 5)
+
+ // Depth 2: the parent segment is stored biased by one, so [0; 0] never
+ // collides with the depth-1 key 0.
+ Assert.Equal(Some 0x10000, tryEncodeOccurrenceKey [ 0; 0 ])
+ Assert.Equal([ 0; 0 ], decodeOccurrenceKey 0x10000)
+ Assert.Equal(Some 0x40007, tryEncodeOccurrenceKey [ 3; 7 ])
+ Assert.Equal([ 3; 7 ], decodeOccurrenceKey 0x40007)
+
+ // Every encodable chain round-trips ([0x1FFE; 0xFFFD] packs to the maximum
+ // key 0x1FFFFFFD that still fits the compressed-integer budget after the
+ // baseline adjustment).
+ for chain in [ [ 0 ]; [ 42 ]; [ 0xFFFF ]; [ 0; 0 ]; [ 3; 7 ]; [ 0x1FFE; 0xFFFD ] ] do
+ match tryEncodeOccurrenceKey chain with
+ | Some key -> Assert.Equal(chain, decodeOccurrenceKey key)
+ | None -> failwith $"expected chain %A{chain} to be encodable"
+
+[]
+let ``Occurrence key packing fails closed past its limits`` () =
+ // Deeper than two segments.
+ Assert.Equal(None, tryEncodeOccurrenceKey [ 1; 2; 3 ])
+ // Empty chain.
+ Assert.Equal(None, tryEncodeOccurrenceKey [])
+ // Ordinal past 16 bits.
+ Assert.Equal(None, tryEncodeOccurrenceKey [ 0x10000 ])
+ Assert.Equal(None, tryEncodeOccurrenceKey [ 0; 0x10000 ])
+ // Parent past the compressed-integer budget (29 bits incl. the bias).
+ Assert.Equal(None, tryEncodeOccurrenceKey [ 0x1FFF; 0 ])
+ // Packed key past the budget even though both segments are individually valid.
+ Assert.Equal(None, tryEncodeOccurrenceKey [ 0x1FFE; 0xFFFF ])
+ // Regression: a large in-range parent whose packed key wraps NEGATIVE in int32
+ // ((0xFFFE + 1) <<< 16). The int32 packing accepted the wrapped key (negative
+ // <= MaxOccurrenceKey), failing open; the int64 packing must reject it.
+ Assert.Equal(None, tryEncodeOccurrenceKey [ 0xFFFE; 0 ])
+ Assert.Equal(None, tryEncodeOccurrenceKey [ 0x7FFF; 0xFFFF ])
+ // Negative ordinals.
+ Assert.Equal(None, tryEncodeOccurrenceKey [ -1 ])
+ Assert.Equal(None, tryEncodeOccurrenceKey [ -1; 0 ])
+
+// -----------------------------------------------------------------------
+// Cross-validation against Roslyn-emitted blobs
+// -----------------------------------------------------------------------
+
+/// C# source with nested capturing lambdas, LINQ lambdas, and an async method, so a
+/// debug build emits all three EnC CDI kinds.
+let private crossValidationSource =
+ """
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Scratch
+{
+ public class Lambdas
+ {
+ public Func MakeAdder(int x)
+ {
+ int y = x + 1;
+ Func inner = a => a + x + y;
+ return b => inner(b) + x;
+ }
+
+ public int UseLinq(IEnumerable items, int threshold)
+ {
+ var filtered = items.Where(i => i > threshold).Select(i => i * 2);
+ return filtered.Sum(i => i + threshold);
+ }
+
+ public async Task ComputeAsync(int x)
+ {
+ await Task.Delay(1);
+ int y = x * 2;
+ await Task.Yield();
+ Func f = a => a + y;
+ return f(x);
+ }
+ }
+}
+"""
+
+/// Builds the cross-validation C# library with the repo SDK (DebugType=portable) and
+/// returns the path of the produced Portable PDB.
+let private buildCSharpScratchPdb () =
+ let workDir =
+ Path.Combine(Path.GetTempPath(), "fsharp-enc-cdi-" + Guid.NewGuid().ToString("N"))
+
+ Directory.CreateDirectory workDir |> ignore
+ let projPath = Path.Combine(workDir, "scratch.csproj")
+ File.WriteAllText(Path.Combine(workDir, "Scratch.cs"), crossValidationSource)
+
+ File.WriteAllText(
+ projPath,
+ $"""
+
+ Library
+ {TestFramework.productTfm}
+ portable
+ false
+ true
+ disable
+
+
+"""
+ )
+
+ let psi = System.Diagnostics.ProcessStartInfo()
+ // Resolve the dotnet host like the rest of the test framework: repo-local .dotnet
+ // first, PATH fallback otherwise (the hand-rolled path misses on some CI images).
+ psi.FileName <- TestFramework.initialConfig.DotNetExe
+ // ProcessStartInfo.ArgumentList does not exist on net472, so build the quoted argument
+ // string by hand (projPath is the only argument that can contain spaces).
+ psi.Arguments <- $"build \"{projPath}\" -c Debug -p:DebugType=portable -v m"
+ // net472 defaults UseShellExecute to true, which is incompatible with stream
+ // redirection; set it explicitly so the Desktop test legs can start the process.
+ psi.UseShellExecute <- false
+ psi.RedirectStandardOutput <- true
+ psi.RedirectStandardError <- true
+ psi.WorkingDirectory <- workDir
+
+ use p = new System.Diagnostics.Process()
+ p.StartInfo <- psi
+ p.Start() |> ignore
+ let stdout = p.StandardOutput.ReadToEnd()
+ let stderr = p.StandardError.ReadToEnd()
+ p.WaitForExit()
+
+ if p.ExitCode <> 0 then
+ failwith $"dotnet build of the C# scratch library failed: {stdout}\n{stderr}"
+
+ let pdbPath = Path.Combine(workDir, "bin", "Debug", TestFramework.productTfm, "scratch.pdb")
+ Assert.True(File.Exists pdbPath, $"expected portable PDB at {pdbPath}")
+ workDir, pdbPath
+
+/// Reads all CustomDebugInformation rows of the given kind from a portable PDB,
+/// returning (parent method token, blob bytes) pairs.
+let private readCdiBlobs (reader: MetadataReader) (kind: Guid) =
+ [ for cdiHandle in reader.CustomDebugInformation do
+ let cdi = reader.GetCustomDebugInformation cdiHandle
+
+ if reader.GetGuid cdi.Kind = kind then
+ let parent = MetadataTokens.GetToken cdi.Parent
+ parent, reader.GetBlobBytes cdi.Value ]
+
+[]
+let ``Roslyn-emitted EnC CDI blobs decode and re-encode byte-for-byte`` () =
+ let workDir, pdbPath = buildCSharpScratchPdb ()
+
+ try
+ use stream = File.OpenRead pdbPath
+ use provider = MetadataReaderProvider.FromPortablePdbStream stream
+ let reader = provider.GetMetadataReader()
+
+ // ---- EnC Lambda and Closure Map ----
+ let lambdaMaps = readCdiBlobs reader PortableCustomDebugInfoKinds.encLambdaAndClosureMap
+ Assert.NotEmpty lambdaMaps
+
+ let mutable totalLambdas = 0
+ let mutable totalClosures = 0
+
+ for _, blob in lambdaMaps do
+ let methodOrdinal, closures, lambdas = deserializeLambdaMap blob
+
+ // Structural sanity: defined ordinal, at least one lambda or closure,
+ // closure ordinals within range.
+ Assert.True(methodOrdinal >= 0, "Roslyn lambda maps carry a defined method ordinal")
+ Assert.True(not (List.isEmpty closures) || not (List.isEmpty lambdas))
+
+ for lambda in lambdas do
+ Assert.InRange(lambda.ClosureOrdinal, MinClosureOrdinal, closures.Length - 1)
+
+ totalLambdas <- totalLambdas + lambdas.Length
+ totalClosures <- totalClosures + closures.Length
+
+ // Byte-for-byte: re-encoding the decoded map must reproduce Roslyn's blob.
+ let reencoded =
+ serializeLambdaMap
+ { EncMethodDebugInformation.Empty with
+ MethodOrdinal = methodOrdinal
+ Closures = closures
+ Lambdas = lambdas }
+
+ Assert.Equal(blob, reencoded)
+
+ // The source has 6 lambdas (2 in MakeAdder, 3 in UseLinq, 1 in ComputeAsync)
+ // and capturing closures in every method.
+ Assert.True(totalLambdas >= 6, $"expected at least 6 lambdas, found {totalLambdas}")
+ Assert.True(totalClosures >= 3, $"expected at least 3 closures, found {totalClosures}")
+
+ // ---- EnC Local Slot Map ----
+ let slotMaps = readCdiBlobs reader PortableCustomDebugInfoKinds.encLocalSlotMap
+ Assert.NotEmpty slotMaps
+
+ let mutable longLivedSlots = 0
+
+ for _, blob in slotMaps do
+ let slots = deserializeLocalSlots blob
+ Assert.NotEmpty slots
+
+ for slot in slots do
+ match slot with
+ | EncLocalSlotInfo.Temp -> ()
+ | EncLocalSlotInfo.Slot(kind, _, ordinal) ->
+ Assert.InRange(kind, 0, MaxSerializableLocalKind)
+ Assert.True(ordinal >= 0)
+ longLivedSlots <- longLivedSlots + 1
+
+ let reencoded =
+ serializeLocalSlots
+ { EncMethodDebugInformation.Empty with
+ LocalSlots = slots }
+
+ Assert.Equal(blob, reencoded)
+
+ Assert.True(longLivedSlots > 0, "expected at least one long-lived local slot")
+
+ // ---- EnC State Machine State Map ----
+ let stateMaps = readCdiBlobs reader PortableCustomDebugInfoKinds.encStateMachineStateMap
+ Assert.NotEmpty stateMaps
+
+ for _, blob in stateMaps do
+ let states = deserializeStateMachineStates blob
+
+ // ComputeAsync has two suspension points (await Task.Delay, await
+ // Task.Yield); the decoder enforces monotone offsets, re-check here.
+ Assert.True(states.Length >= 2, $"expected at least 2 states, found {states.Length}")
+
+ let offsets = states |> List.map (fun s -> s.SyntaxOffset)
+ Assert.Equal(List.sort offsets, offsets)
+
+ let reencoded =
+ serializeStateMachineStates
+ { EncMethodDebugInformation.Empty with
+ StateMachineStates = states }
+
+ Assert.Equal(blob, reencoded)
+ finally
+ try
+ Directory.Delete(workDir, true)
+ with _ ->
+ ()
+
+// -----------------------------------------------------------------------
+// Synthetic plumbing: exercise the real ILBinaryWriter/PortablePdbGenerator path
+// (no hot reload flag, no session machinery) with a synthetic CDI row map.
+// -----------------------------------------------------------------------
+
+module private Plumbing =
+
+ // A real primary-assembly reference (this process's own corelib) so ilg.typ_Object
+ // resolves to an external TypeRef; the IL writer requires every type's 'extends' to
+ // resolve to a real System.Object, even one it never loads.
+ let private primaryAssemblyRef = ILAssemblyRef.FromAssemblyName(typeof.Assembly.GetName())
+
+ let private ilg =
+ mkILGlobals (ILScopeRef.Assembly primaryAssemblyRef, [], ILScopeRef.Assembly primaryAssemblyRef)
+
+ let private mkMethod (name: string) (body: MethodBody) : ILMethodDef =
+ mkILNonGenericStaticMethod (name, ILMemberAccess.Public, [], mkILReturn ILType.Void, body)
+
+ let private mkType (typeName: string) (methods: (string * MethodBody) list) : ILTypeDef =
+ let methods = methods |> List.map (fun (name, body) -> mkMethod name body) |> mkILMethods
+
+ ILTypeDef(
+ typeName,
+ TypeAttributes.Public,
+ ILTypeDefLayout.Auto,
+ [],
+ [],
+ Some ilg.typ_Object,
+ methods,
+ mkILTypeDefs [],
+ mkILFields [],
+ emptyILMethodImpls,
+ mkILEvents [],
+ mkILProperties [],
+ emptyILSecurityDecls,
+ emptyILCustomAttrsStored
+ )
+
+ /// Builds a minimal in-memory module with one type per (typeName, methodNames) pair.
+ /// Two types may each declare a method of the same name: the IL writer's per-type
+ /// method table forbids two same-named methods of the same arity *within one type*
+ /// (unrelated to CDI), but the CDI name-keying this test exercises is per-assembly,
+ /// so cross-type name clashes are exactly the ambiguous case to cover.
+ let buildModuleOfMethodBodies (types: (string * (string * MethodBody) list) list) : ILModuleDef =
+ let typeDefs = types |> List.map (fun (typeName, methods) -> mkType typeName methods)
+
+ let assemblyName = "EncCdiPlumbing_" + Guid.NewGuid().ToString("N")
+
+ mkILSimpleModule
+ assemblyName
+ assemblyName
+ true
+ (4, 0)
+ false
+ (mkILTypeDefs typeDefs)
+ None
+ None
+ 0
+ (mkILExportedTypes [])
+ "v4.0.30319" // Non-empty: pins the metadata version explicitly rather than relying on primaryAssemblyRef's.
+
+ let buildModuleOfTypes (types: (string * string list) list) : ILModuleDef =
+ types
+ |> List.map (fun (typeName, methodNames) ->
+ typeName, methodNames |> List.map (fun name -> name, MethodBody.Abstract))
+ |> buildModuleOfMethodBodies
+
+ /// Builds a minimal in-memory module with one type "T" declaring 'methodNames'.
+ let buildModule (methodNames: string list) : ILModuleDef = buildModuleOfTypes [ "T", methodNames ]
+
+ /// Writes 'modul' through the same in-memory ILBinaryWriter entry point fsi.fs uses for
+ /// dynamic assembly emission, attaching 'methodCustomDebugInfoRows' as the CDI side
+ /// channel. No hot reload flag or session state is involved.
+ let writeInMemory (modul: ILModuleDef) (methodCustomDebugInfoRows: Map) =
+ let options: options =
+ {
+ ilg = ilg
+ outfile = "test.dll"
+ pdbfile = Some "test.pdb"
+ portablePDB = true
+ embeddedPDB = false
+ embedAllSource = false
+ embedSourceList = []
+ allGivenSources = []
+ sourceLink = ""
+ checksumAlgorithm = HashAlgorithm.Sha256
+ signer = None
+ emitTailcalls = true
+ deterministic = false
+ dumpDebugInfo = false
+ referenceAssemblyOnly = false
+ referenceAssemblyAttribOpt = None
+ referenceAssemblySignatureHash = None
+ pathMap = PathMap.empty
+ methodCustomDebugInfoRows = methodCustomDebugInfoRows
+ }
+
+ match WriteILBinaryInMemory(options, modul, id) with
+ | assemblyBytes, Some pdbBytes -> assemblyBytes, pdbBytes
+ | _, None -> failwith "expected a portable PDB to be produced"
+
+ type CdiRow =
+ {
+ MethodName: string option
+ Kind: Guid
+ Blob: byte[]
+ }
+
+ /// All method-parented CustomDebugInformation rows in the produced PDB, read back with
+ /// System.Reflection.Metadata (independent of this codebase's own decoders), resolving
+ /// each row's parent MethodDef token to its name via the companion assembly image.
+ let readAllCdiRows (assemblyBytes: byte[]) (pdbBytes: byte[]) : CdiRow list =
+ use peReader = new PEReader(ImmutableArray.CreateRange assemblyBytes)
+ let peMdReader = peReader.GetMetadataReader()
+
+ use pdbProvider = MetadataReaderProvider.FromPortablePdbImage(ImmutableArray.CreateRange pdbBytes)
+ let pdbMdReader = pdbProvider.GetMetadataReader()
+
+ let methodTokenToName =
+ [ for h: MethodDefinitionHandle in peMdReader.MethodDefinitions ->
+ MetadataTokens.GetToken(MethodDefinitionHandle.op_Implicit h: EntityHandle),
+ peMdReader.GetString(peMdReader.GetMethodDefinition(h).Name) ]
+ |> Map.ofList
+
+ [ for cdiHandle in pdbMdReader.CustomDebugInformation do
+ let cdi = pdbMdReader.GetCustomDebugInformation cdiHandle
+
+ if cdi.Parent.Kind = HandleKind.MethodDefinition then
+ {
+ MethodName = Map.tryFind (MetadataTokens.GetToken cdi.Parent) methodTokenToName
+ Kind = pdbMdReader.GetGuid cdi.Kind
+ Blob = pdbMdReader.GetBlobBytes cdi.Value
+ } ]
+
+[]
+let ``Synthetic CustomDebugInformation row attaches to the right MethodDef`` () =
+ let modul = Plumbing.buildModule [ "Foo"; "Bar" ]
+
+ let blob =
+ serializeLambdaMap
+ { EncMethodDebugInformation.Empty with
+ MethodOrdinal = 0
+ Closures = [ { SyntaxOffset = 0 } ]
+ Lambdas = [ { SyntaxOffset = 5; ClosureOrdinal = 0 } ] }
+
+ let rows =
+ Map.ofList [ "Foo", [ { KindGuid = PortableCustomDebugInfoKinds.encLambdaAndClosureMap; Blob = blob } ] ]
+
+ let assemblyBytes, pdbBytes = Plumbing.writeInMemory modul rows
+ let cdiRows = Plumbing.readAllCdiRows assemblyBytes pdbBytes
+
+ let row = Assert.Single cdiRows
+ Assert.Equal(Some "Foo", row.MethodName)
+ Assert.Equal(PortableCustomDebugInfoKinds.encLambdaAndClosureMap, row.Kind)
+ Assert.Equal(blob, row.Blob)
+
+ // Full circle: the codec decodes exactly what was written.
+ let methodOrdinal, closures, lambdas = deserializeLambdaMap row.Blob
+ Assert.Equal(0, methodOrdinal)
+ Assert.Equal([ { SyntaxOffset = 0 } ], closures)
+ Assert.Equal([ { SyntaxOffset = 5; ClosureOrdinal = 0 } ], lambdas)
+
+[]
+let ``Empty map produces zero CustomDebugInformation rows`` () =
+ let modul = Plumbing.buildModule [ "Foo" ]
+ let assemblyBytes, pdbBytes = Plumbing.writeInMemory modul Map.empty
+ Assert.Empty(Plumbing.readAllCdiRows assemblyBytes pdbBytes)
+
+[]
+let ``A method name absent from the module attaches nothing`` () =
+ // Fail closed, matching the feature this codec ports from: an unresolvable name is
+ // silently dropped rather than raising, so it can never attach to the wrong method.
+ let modul = Plumbing.buildModule [ "Foo" ]
+
+ let blob =
+ serializeStateMachineStates
+ { EncMethodDebugInformation.Empty with
+ StateMachineStates = [ { StateNumber = 0; SyntaxOffset = 1 } ] }
+
+ let rows =
+ Map.ofList [ "DoesNotExist", [ { KindGuid = PortableCustomDebugInfoKinds.encStateMachineStateMap; Blob = blob } ] ]
+
+ let assemblyBytes, pdbBytes = Plumbing.writeInMemory modul rows
+ Assert.Empty(Plumbing.readAllCdiRows assemblyBytes pdbBytes)
+
+[]
+let ``An ambiguous method name attaches to neither method`` () =
+ // Two distinct types each declaring a "Dup" method: the CDI name-keying in
+ // PortablePdbGenerator is per-assembly (IL method name only, not qualified by
+ // declaring type), so this reproduces the ambiguous case without hitting the
+ // unrelated IL writer invariant that forbids two same-named/same-arity methods
+ // within a single type.
+ let modul = Plumbing.buildModuleOfTypes [ "T1", [ "Dup" ]; "T2", [ "Dup" ] ]
+
+ let blob =
+ serializeStateMachineStates
+ { EncMethodDebugInformation.Empty with
+ StateMachineStates = [ { StateNumber = 0; SyntaxOffset = 1 } ] }
+
+ let rows =
+ Map.ofList [ "Dup", [ { KindGuid = PortableCustomDebugInfoKinds.encStateMachineStateMap; Blob = blob } ] ]
+
+ let assemblyBytes, pdbBytes = Plumbing.writeInMemory modul rows
+ Assert.Empty(Plumbing.readAllCdiRows assemblyBytes pdbBytes)
+
+[]
+let ``A method name shared with unavailable metadata attaches to neither method`` () =
+ let modul =
+ Plumbing.buildModuleOfMethodBodies
+ [ "T1", [ "Dup", MethodBody.Abstract ]
+ "T2", [ "Dup", MethodBody.NotAvailable ] ]
+
+ let blob =
+ serializeStateMachineStates
+ { EncMethodDebugInformation.Empty with
+ StateMachineStates = [ { StateNumber = 0; SyntaxOffset = 1 } ] }
+
+ let rows =
+ Map.ofList [ "Dup", [ { KindGuid = PortableCustomDebugInfoKinds.encStateMachineStateMap; Blob = blob } ] ]
+
+ let assemblyBytes, pdbBytes = Plumbing.writeInMemory modul rows
+ Assert.Empty(Plumbing.readAllCdiRows assemblyBytes pdbBytes)
diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/AccessibilityAnnotations/PermittedLocations/PermittedLocations.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/AccessibilityAnnotations/PermittedLocations/PermittedLocations.fs
index 7f4c02ab56f..b8ded929963 100644
--- a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/AccessibilityAnnotations/PermittedLocations/PermittedLocations.fs
+++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/AccessibilityAnnotations/PermittedLocations/PermittedLocations.fs
@@ -131,9 +131,9 @@ module AccessibilityAnnotations_PermittedLocations =
|> shouldFail
|> withDiagnostics [
(Error 531, Line 11, Col 13, Line 11, Col 20, "Accessibility modifiers should come immediately prior to the identifier naming a construct")
- (Error 58, Line 12, Col 23, Line 12, Col 26, "Unexpected syntax or possible incorrect indentation: this token is offside of context started at position (11:23). Try indenting this further.\nTo continue using non-conforming indentation, pass the '--strict-indentation-' flag to the compiler, or set the language version to F# 7.")
+ (Error 58, Line 12, Col 23, Line 12, Col 26, "Unexpected syntax or possible incorrect indentation: this token is offside of context started at position (11:23). Try indenting this further.")
(Error 531, Line 12, Col 13, Line 12, Col 19, "Accessibility modifiers should come immediately prior to the identifier naming a construct")
- (Error 58, Line 13, Col 23, Line 13, Col 26, "Unexpected syntax or possible incorrect indentation: this token is offside of context started at position (12:23). Try indenting this further.\nTo continue using non-conforming indentation, pass the '--strict-indentation-' flag to the compiler, or set the language version to F# 7.")
+ (Error 58, Line 13, Col 23, Line 13, Col 26, "Unexpected syntax or possible incorrect indentation: this token is offside of context started at position (12:23). Try indenting this further.")
(Error 531, Line 13, Col 13, Line 13, Col 21, "Accessibility modifiers should come immediately prior to the identifier naming a construct")
]
diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/AttributeUsage/AttributeResolutionInRecursiveScopes.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/AttributeUsage/AttributeResolutionInRecursiveScopes.fs
index 42390f21aa0..c33f6808bdb 100644
--- a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/AttributeUsage/AttributeResolutionInRecursiveScopes.fs
+++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/AttributeUsage/AttributeResolutionInRecursiveScopes.fs
@@ -55,7 +55,7 @@ type CustomAttribute() =
|> typecheck
|> shouldSucceed
- // https://github.com/dotnet/fsharp/issues/5795 - attribute on union case in rec module is not yet resolved
+ // https://github.com/dotnet/fsharp/issues/5795 - attribute on union case in rec module now resolves
[]
let ``Issue 5795 - attribute on union case in rec module`` () =
FSharp """
@@ -67,11 +67,9 @@ type CustomAttribute() =
type A = | [] A
"""
|> typecheck
- |> shouldFail
- |> withDiagnostics
- [ Error 1133, Line 7, Col 14, Line 7, Col 29, "No constructors are available for the type 'CustomAttribute'" ]
+ |> shouldSucceed
- // https://github.com/dotnet/fsharp/issues/5795 - attribute on type parameter in rec module is not yet resolved
+ // https://github.com/dotnet/fsharp/issues/5795 - attribute on type parameter in rec module now resolves
[]
let ``Issue 5795 - attribute on type parameter in rec module`` () =
FSharp """
@@ -83,8 +81,7 @@ type CustomAttribute() =
type B<[]'a> = | B of 'a
"""
|> typecheck
- |> shouldFail
- |> withErrorCode 39
+ |> shouldSucceed
// Nested module case: open inside outer module, attribute on inner module
[]
diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/Basic/Basic.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/Basic/Basic.fs
index dde97126f13..d47e20daefc 100644
--- a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/Basic/Basic.fs
+++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/Basic/Basic.fs
@@ -439,7 +439,7 @@ if Convert.ToString(prop, Globalization.CultureInfo.InvariantCulture) <> "B" the
]
[]
- let ``StructLayoutAttribute has size=1 for struct DUs with no instance fields`` () =
+ let ``StructLayoutAttribute doesn't have size=1 for multi-case struct DUs with no instance fields`` () =
Fsx """
[] type Option<'T> = None | Some
"""
@@ -455,8 +455,6 @@ if Convert.ToString(prop, Globalization.CultureInfo.InvariantCulture) <> "B" the
[runtime]System.IComparable,
[runtime]System.Collections.IStructuralComparable
{
- .pack 0
- .size 1
.custom instance void [FSharp.Core]Microsoft.FSharp.Core.StructAttribute::.ctor() = ( 01 00 00 00 )
.custom instance void [runtime]System.Diagnostics.DebuggerDisplayAttribute::.ctor(string) = ( 01 00 15 7B 5F 5F 44 65 62 75 67 44 69 73 70 6C
61 79 28 29 2C 6E 71 7D 00 00 )
@@ -468,4 +466,28 @@ if Convert.ToString(prop, Globalization.CultureInfo.InvariantCulture) <> "B" the
.field public static literal int32 Some = int32(0x00000001)
}
"""
- ]
\ No newline at end of file
+ ]
+
+ []
+ let ``StructLayoutAttribute doesn't have size=1 for single-case struct DU`` () =
+ Fsx """
+ [] type X = | Y
+ """
+ |> compile
+ |> shouldSucceed
+ |> verifyIL [
+ """
+ .class sequential autochar serializable sealed nested public beforefieldinit X
+ extends [runtime]System.ValueType
+ implements class [runtime]System.IEquatable`1,
+ [runtime]System.Collections.IStructuralEquatable,
+ class [runtime]System.IComparable`1,
+ [runtime]System.IComparable,
+ [runtime]System.Collections.IStructuralComparable
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.StructAttribute::.ctor() = ( 01 00 00 00 )
+ .custom instance void [runtime]System.Diagnostics.DebuggerDisplayAttribute::.ctor(string) = ( 01 00 15 7B 5F 5F 44 65 62 75 67 44 69 73 70 6C
+ 61 79 28 29 2C 6E 71 7D 00 00 )
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 01 00 00 00 00 00 )
+ """
+ ]
diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/LetBindings/Basic/Basic.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/LetBindings/Basic/Basic.fs
index b59d28cbdd2..0e2ef117ea6 100644
--- a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/LetBindings/Basic/Basic.fs
+++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/LetBindings/Basic/Basic.fs
@@ -76,7 +76,7 @@ module LetBindings_Basic =
|> verifyCompile
|> shouldFail
|> withDiagnostics [
- (Error 58, Line 10, Col 1, Line 10, Col 5, "Unexpected syntax or possible incorrect indentation: this token is offside of context started at position (8:1). Try indenting this further.\nTo continue using non-conforming indentation, pass the '--strict-indentation-' flag to the compiler, or set the language version to F# 7.")
+ (Error 58, Line 10, Col 1, Line 10, Col 5, "Unexpected syntax or possible incorrect indentation: this token is offside of context started at position (8:1). Try indenting this further.")
(Error 10, Line 10, Col 6, Line 10, Col 7, "Unexpected start of structured construct in expression")
(Error 583, Line 9, Col 5, Line 9, Col 6, "Unmatched '('")
(Error 10, Line 10, Col 16, Line 10, Col 17, "Unexpected symbol ')' in implementation file")
diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/Constraints/Unmanaged.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/Constraints/Unmanaged.fs
index 712333340fa..8340ac9be7f 100644
--- a/tests/FSharp.Compiler.ComponentTests/Conformance/Constraints/Unmanaged.fs
+++ b/tests/FSharp.Compiler.ComponentTests/Conformance/Constraints/Unmanaged.fs
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
+// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
namespace Conformance.Constraints
diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/LexicalFiltering/OffsideExceptions/OffsideExceptions.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/LexicalFiltering/OffsideExceptions/OffsideExceptions.fs
index 9dc910ba249..39db4d639a8 100644
--- a/tests/FSharp.Compiler.ComponentTests/Conformance/LexicalFiltering/OffsideExceptions/OffsideExceptions.fs
+++ b/tests/FSharp.Compiler.ComponentTests/Conformance/LexicalFiltering/OffsideExceptions/OffsideExceptions.fs
@@ -229,7 +229,7 @@ module A
EndLine = 4
EndColumn = 6 }
Message =
- "Unexpected syntax or possible incorrect indentation: this token is offside of context started at position (3:5). Try indenting this further.\nTo continue using non-conforming indentation, pass the '--strict-indentation-' flag to the compiler, or set the language version to F# 7."
+ "Unexpected syntax or possible incorrect indentation: this token is offside of context started at position (3:5). Try indenting this further."
} |> ignore
[]
diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/LexicalFiltering/OffsideExceptions/RelaxWhitespace2.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/LexicalFiltering/OffsideExceptions/RelaxWhitespace2.fs
index ef83e1ba871..4b90ca2988a 100644
--- a/tests/FSharp.Compiler.ComponentTests/Conformance/LexicalFiltering/OffsideExceptions/RelaxWhitespace2.fs
+++ b/tests/FSharp.Compiler.ComponentTests/Conformance/LexicalFiltering/OffsideExceptions/RelaxWhitespace2.fs
@@ -3434,7 +3434,7 @@ let c = f' {
let d = f' {|
X = 2 (* FS0058 Possible incorrect indentation:
this token is offside of context started at position (12:11).
-Try indenting this further.\nTo continue using non-conforming indentation, pass the '--strict-indentation-' flag to the compiler, or set the language version to F# 7 *)
+Try indenting this further. *)
|}
let e = f' {|
X = 2 // Indenting further is needed
diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/Spreads/RecordSpreads.fsx b/tests/FSharp.Compiler.ComponentTests/Conformance/Spreads/RecordSpreads.fsx
new file mode 100644
index 00000000000..c83a88a43ab
--- /dev/null
+++ b/tests/FSharp.Compiler.ComponentTests/Conformance/Spreads/RecordSpreads.fsx
@@ -0,0 +1,86 @@
+#r "SpreadInlineLib.dll"
+
+open System
+let errors = ResizeArray()
+let check label cond = if not cond then errors.Add label
+
+type Pt = { X : int; Y : int }
+type Lbl = { A : int; B : int }
+
+module ``Units of measure preserved through overriding spread`` =
+ [] type m
+ type Tagged = { D : int; Note : string }
+ check "D measure stripped" ({ ...{ D = 5; Note = "a" }; D = 9 }.D = 9)
+
+module ``Type alias as spread source`` =
+ type PtAlias = Pt
+ type FromAlias = { ...PtAlias; Z : int }
+ let v : FromAlias = { ...{ X = 10; Y = 20 }; Z = 30 }
+ check "alias source dropped fields" (v.X = 10 && v.Z = 30)
+
+module ``Elaborated tree shape inside FSharp Quotations`` =
+ open Microsoft.FSharp.Quotations.Patterns
+ let rec args expr =
+ match expr with
+ | Let (_, _, body) -> args body
+ | NewRecord (_, a) -> Some a.Length
+ | _ -> None
+ let p = { X = 1; Y = 2 }
+ check "quotation record/anon shape" (args <@ { ...p; Y = 3 } @> = Some 2 && args <@ {| ...p; W = 5 |} @> = Some 3)
+
+module ``Spread inside seq, async and task state machines`` =
+ let b = { A = 1; B = 2 }
+ let fromSeq = seq { for i in 1..2 -> { ...b; A = i } } |> Seq.toList
+ check "seq spread wrong" (fromSeq.[1].A = 2)
+ check "async return wrong" ((async { return { ...b; A = 9 } } |> Async.RunSynchronously).A = 9)
+ check "task return wrong" ((task { return { ...b; A = 7 } }).Result.A = 7)
+
+module ``CLIMutable target emits settable IL properties for spread-carried fields`` =
+ type Src = { A : int; B : int }
+ [] type Dst = { ...Src; C : int }
+ let hasCli (t: Type) = t.GetCustomAttributes(typeof, false).Length > 0
+ let settable n = typeof.GetProperty(n: string).CanWrite
+ check "CLIMutable attr leaked to Src" (not (hasCli typeof))
+ check "Dst missing CLIMutable" (hasCli typeof)
+ check "settable A/B/C" (settable "A" && settable "B" && settable "C")
+ check "Dst C wrong" (({ ...{ A = 1; B = 2 }; C = 3 } : Dst).C = 3)
+
+module ``Type-level attributes do not propagate from spread source`` =
+ [] type Src = { A : int; B : int }
+ type Plain = { ...Src; C : int }
+ let has<'a when 'a :> Attribute> (t: Type) = t.GetCustomAttributes(typeof<'a>, false).Length > 0
+ check "CLIMutable propagated to Plain" (not (has typeof))
+ check "NoComparison propagated to Plain" (not (has typeof))
+ check "Src lost CLIMutable" (has typeof)
+
+module ``Mutable field carried via spread, then overridden`` =
+ type R = { mutable M : int; Name : string }
+ check "mutable override wrong" ({ ...{ M = 1; Name = "a" }; M = 10 }.M = 10)
+
+module ``SRTP resolves member carried by the spread source`` =
+ let inline getB< ^T when ^T : (member B : int)> (x: ^T) = (^T : (member B : int) x)
+ check "SRTP getB <> 6" (getB {| ...{| A = 5; B = 6 |}; A = 7 |} = 6)
+
+module ``Inline spread elaboration across an assembly boundary`` =
+ let r = SpreadInlineLib.bump { SpreadInlineLib.Lbl.A = 0; B = 7 }
+ check "cross-assembly bump A/B" (r.A = 99 && r.B = 7)
+
+module ``Property-get expression as spread source`` =
+ type Holder() = member _.P = { A = 1; B = 2 }
+ let r = { ...(Holder()).P; B = 9 }
+ check "property-get source dropped fields" (r.A = 1 && r.B = 9)
+
+module ``Field-level attribute carries from spread source to target`` =
+ type Src = { [] A : int; B : int }
+ type Dst = { ...Src; C : int }
+ let obsolete (t: Type) = t.GetProperty("A").GetCustomAttributes(typeof, false).Length
+ check "field attr not carried Src/Dst" (obsolete typeof = 1 && obsolete typeof = 1)
+
+module ``Linear non-mutual transitive spread chain`` =
+ type A = { Z : int }
+ type B = { ...A; Y : int }
+ type C = { ...B; X : int }
+ let c : C = { Z = 1; Y = 2; X = 3 }
+ check "transitive chain dropped fields" (c.Z = 1 && c.X = 3)
+if errors.Count > 0 then
+ failwithf "%d failures:\n%s" errors.Count (String.concat "\n" errors)
diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/Spreads/RecordSpreadsTests.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/Spreads/RecordSpreadsTests.fs
new file mode 100644
index 00000000000..cea7e7955c6
--- /dev/null
+++ b/tests/FSharp.Compiler.ComponentTests/Conformance/Spreads/RecordSpreadsTests.fs
@@ -0,0 +1,28 @@
+module Conformance.Spreads.Records
+
+open System.IO
+open Xunit
+open FSharp.Test
+open FSharp.Test.Compiler
+
+[]
+let SupportedLangVersion = "preview"
+
+let inlineLib =
+ FsFromPath (Path.Combine (__SOURCE_DIRECTORY__, "SpreadInlineLib.fs"))
+ |> withLangVersion SupportedLangVersion
+ |> withName "SpreadInlineLib"
+ |> asLibrary
+
+let verifyCompileAndRun compilation =
+ compilation
+ |> asExe
+ |> withLangVersion SupportedLangVersion
+ |> compileAndRun
+
+[]
+let ``RecordSpreads_fsx`` compilation =
+ compilation
+ |> withReferences [inlineLib]
+ |> verifyCompileAndRun
+ |> shouldSucceed
diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/Spreads/SpreadInlineLib.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/Spreads/SpreadInlineLib.fs
new file mode 100644
index 00000000000..e157ae30f5b
--- /dev/null
+++ b/tests/FSharp.Compiler.ComponentTests/Conformance/Spreads/SpreadInlineLib.fs
@@ -0,0 +1,7 @@
+module SpreadInlineLib
+// Library compiled to its own assembly. The inline body below is serialized
+// into the assembly's pickled TypedTree and re-elaborated at the caller's
+// site in another assembly (Spreading_v1.fsx), exercising the spread
+// elaboration across the TypedTreePickle boundary.
+type Lbl = { A : int; B : int }
+let inline bump (x: Lbl) = { ...x; A = 99 }
diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/Types/RecordTypes/AnonymousRecords.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/Types/RecordTypes/AnonymousRecords.fs
index beef862b27a..dce13c8da38 100644
--- a/tests/FSharp.Compiler.ComponentTests/Conformance/Types/RecordTypes/AnonymousRecords.fs
+++ b/tests/FSharp.Compiler.ComponentTests/Conformance/Types/RecordTypes/AnonymousRecords.fs
@@ -446,7 +446,7 @@ let v = {| A = 1; A = 2 |}
|> compile
|> shouldFail
|> withDiagnostics [
- (Error 3522, Line 2, Col 12, Line 2, Col 13, "The field 'A' appears multiple times in this record expression.")
+ (Error 3522, Line 2, Col 19, Line 2, Col 24, "The field 'A' appears multiple times in this record expression.")
]
[]
@@ -457,8 +457,8 @@ let v = {| A = 1; A = 2; A = 3 |}
|> compile
|> shouldFail
|> withDiagnostics [
- (Error 3522, Line 2, Col 12, Line 2, Col 13, "The field 'A' appears multiple times in this record expression.")
- (Error 3522, Line 2, Col 19, Line 2, Col 20, "The field 'A' appears multiple times in this record expression.")
+ Error 3522, Line 2, Col 19, Line 2, Col 24, "The field 'A' appears multiple times in this record expression."
+ Error 3522, Line 2, Col 26, Line 2, Col 31, "The field 'A' appears multiple times in this record expression."
]
[]
@@ -469,8 +469,8 @@ let v = {| A = 0; B = 2; A = 5; B = 6 |}
|> compile
|> shouldFail
|> withDiagnostics [
- (Error 3522, Line 2, Col 12, Line 2, Col 13, "The field 'A' appears multiple times in this record expression.")
- (Error 3522, Line 2, Col 19, Line 2, Col 20, "The field 'B' appears multiple times in this record expression.")
+ Error 3522, Line 2, Col 26, Line 2, Col 31, "The field 'A' appears multiple times in this record expression."
+ Error 3522, Line 2, Col 33, Line 2, Col 38, "The field 'B' appears multiple times in this record expression."
]
[]
@@ -481,7 +481,7 @@ let v = {| A = 2; C = "W"; A = 8; B = 6 |}
|> compile
|> shouldFail
|> withDiagnostics [
- (Error 3522, Line 2, Col 12, Line 2, Col 13, "The field 'A' appears multiple times in this record expression.")
+ Error 3522, Line 2, Col 28, Line 2, Col 33, "The field 'A' appears multiple times in this record expression."
]
[]
@@ -492,8 +492,8 @@ let v = {| A = 0; C = ""; A = 1; B = 2; A = 5 |}
|> compile
|> shouldFail
|> withDiagnostics [
- (Error 3522, Line 2, Col 12, Line 2, Col 13, "The field 'A' appears multiple times in this record expression.")
- (Error 3522, Line 2, Col 27, Line 2, Col 28, "The field 'A' appears multiple times in this record expression.")
+ Error 3522, Line 2, Col 27, Line 2, Col 32, "The field 'A' appears multiple times in this record expression."
+ Error 3522, Line 2, Col 41, Line 2, Col 46, "The field 'A' appears multiple times in this record expression."
]
[]
@@ -504,8 +504,8 @@ let v = {| ``A`` = 0; B = 5; A = ""; B = 0 |}
|> compile
|> shouldFail
|> withDiagnostics [
- (Error 3522, Line 2, Col 12, Line 2, Col 17, "The field 'A' appears multiple times in this record expression.")
- (Error 3522, Line 2, Col 23, Line 2, Col 24, "The field 'B' appears multiple times in this record expression.")
+ Error 3522, Line 2, Col 30, Line 2, Col 36, "The field 'A' appears multiple times in this record expression."
+ Error 3522, Line 2, Col 38, Line 2, Col 43, "The field 'B' appears multiple times in this record expression."
]
[]
diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/Types/RecordTypes/RecordTypes.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/Types/RecordTypes/RecordTypes.fs
index 3bae9db5802..8e8bd6a1dd6 100644
--- a/tests/FSharp.Compiler.ComponentTests/Conformance/Types/RecordTypes/RecordTypes.fs
+++ b/tests/FSharp.Compiler.ComponentTests/Conformance/Types/RecordTypes/RecordTypes.fs
@@ -441,7 +441,7 @@ module RecordTypes =
|> typecheck
|> shouldFail
|> withDiagnostics [
- (Error 668, Line 4, Col 16, Line 4, Col 17, "The field 'B' appears multiple times in this record expression or pattern")
+ Error 668, Line 4, Col 25, Line 4, Col 32, "The field 'B' appears multiple times in this record expression or pattern"
]
[]
@@ -454,8 +454,8 @@ module RecordTypes =
|> typecheck
|> shouldFail
|> withDiagnostics [
- (Error 668, Line 4, Col 16, Line 4, Col 17, "The field 'B' appears multiple times in this record expression or pattern")
- (Error 668, Line 4, Col 25, Line 4, Col 26, "The field 'B' appears multiple times in this record expression or pattern")
+ Error 668, Line 4, Col 25, Line 4, Col 32, "The field 'B' appears multiple times in this record expression or pattern"
+ Error 668, Line 4, Col 34, Line 4, Col 41, "The field 'B' appears multiple times in this record expression or pattern"
]
[]
@@ -468,8 +468,8 @@ module RecordTypes =
|> typecheck
|> shouldFail
|> withDiagnostics [
- (Error 668, Line 4, Col 16, Line 4, Col 17, "The field 'A' appears multiple times in this record expression or pattern")
- (Error 668, Line 4, Col 23, Line 4, Col 24, "The field 'B' appears multiple times in this record expression or pattern")
+ Error 668, Line 4, Col 30, Line 4, Col 35, "The field 'A' appears multiple times in this record expression or pattern"
+ Error 668, Line 4, Col 37, Line 4, Col 42, "The field 'B' appears multiple times in this record expression or pattern"
]
[]
@@ -482,7 +482,7 @@ module RecordTypes =
|> typecheck
|> shouldFail
|> withDiagnostics [
- (Error 668, Line 4, Col 16, Line 4, Col 17, "The field 'A' appears multiple times in this record expression or pattern")
+ Error 668, Line 4, Col 31, Line 4, Col 36, "The field 'A' appears multiple times in this record expression or pattern"
]
[]
@@ -495,8 +495,8 @@ module RecordTypes =
|> typecheck
|> shouldFail
|> withDiagnostics [
- (Error 668, Line 4, Col 16, Line 4, Col 17, "The field 'A' appears multiple times in this record expression or pattern")
- (Error 668, Line 4, Col 31, Line 4, Col 32, "The field 'A' appears multiple times in this record expression or pattern")
+ Error 668, Line 4, Col 31, Line 4, Col 36, "The field 'A' appears multiple times in this record expression or pattern"
+ Error 668, Line 4, Col 45, Line 4, Col 50, "The field 'A' appears multiple times in this record expression or pattern"
]
[]
@@ -509,8 +509,8 @@ module RecordTypes =
|> typecheck
|> shouldFail
|> withDiagnostics [
- (Error 668, Line 4, Col 16, Line 4, Col 21, "The field 'A' appears multiple times in this record expression or pattern")
- (Error 668, Line 4, Col 27, Line 4, Col 28, "The field 'B' appears multiple times in this record expression or pattern")
+ Error 668, Line 4, Col 34, Line 4, Col 39, "The field 'A' appears multiple times in this record expression or pattern"
+ Error 668, Line 4, Col 41, Line 4, Col 46, "The field 'B' appears multiple times in this record expression or pattern"
]
[]
diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/Types/UnionTypes/UnionTypes.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/Types/UnionTypes/UnionTypes.fs
index 7d5db82abde..49c5b41d7cc 100644
--- a/tests/FSharp.Compiler.ComponentTests/Conformance/Types/UnionTypes/UnionTypes.fs
+++ b/tests/FSharp.Compiler.ComponentTests/Conformance/Types/UnionTypes/UnionTypes.fs
@@ -608,7 +608,7 @@ module UnionTypes =
|> verifyCompile
|> shouldFail
|> withDiagnostics [
- (Error 58, Line 9, Col 1, Line 9, Col 2, "Unexpected syntax or possible incorrect indentation: this token is offside of context started at position (8:19). Try indenting this further.\nTo continue using non-conforming indentation, pass the '--strict-indentation-' flag to the compiler, or set the language version to F# 7.")
+ (Error 58, Line 9, Col 1, Line 9, Col 2, "Unexpected syntax or possible incorrect indentation: this token is offside of context started at position (8:19). Try indenting this further.")
(Error 547, Line 8, Col 24, Line 8, Col 33, "A type definition requires one or more members or other declarations. If you intend to define an empty class, struct or interface, then use 'type ... = class end', 'interface end' or 'struct end'.")
(Error 10, Line 9, Col 1, Line 9, Col 2, "Unexpected symbol '|' in implementation file")
]
diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/CompilerGeneratedNameDeterminism.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/CompilerGeneratedNameDeterminism.fs
new file mode 100644
index 00000000000..ed66a783e46
--- /dev/null
+++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/CompilerGeneratedNameDeterminism.fs
@@ -0,0 +1,136 @@
+namespace EmittedIL
+
+open System
+open System.IO
+open System.Reflection
+open System.Reflection.Metadata
+open System.Reflection.PortableExecutable
+open Xunit
+
+open FSharp.Test.Compiler
+
+module CompilerGeneratedNameDeterminismTests =
+
+ let private source =
+ """
+module GeneratedNameDeterminismSample
+
+open System.Threading.Tasks
+
+let makeAdder x =
+ let inner y = x + y
+ inner
+
+let asyncValue () =
+ async {
+ let! value = async { return 1 }
+ return value + 1
+ }
+
+let taskValue () =
+ task {
+ let! value = Task.FromResult 1
+ return value + 1
+ }
+
+type Builder() =
+ member _.Bind(x, f) = f x
+ member _.Return(x) = x
+
+let builder = Builder()
+
+let computed value =
+ builder {
+ let! x = value
+ return x + 1
+ }
+"""
+
+ let private getOutputPath = function
+ | CompilationResult.Success success ->
+ match success.OutputPath with
+ | Some path -> path
+ | None -> failwith "Compilation did not produce an output path."
+ | CompilationResult.Failure failure ->
+ failwithf "Compilation was expected to succeed, but failed with: %A" failure.Diagnostics
+
+ let private compileLibrary outputDirectory =
+ FSharp source
+ |> withOutputDirectory (Some(DirectoryInfo outputDirectory))
+ |> withOptions [ "--debug:portable"; "--deterministic"; "--optimize-" ]
+ |> asLibrary
+ |> compile
+ |> shouldSucceed
+ |> getOutputPath
+
+ let private typeName (reader: MetadataReader) (handle: TypeDefinitionHandle) =
+ let rec buildName (handle: TypeDefinitionHandle) =
+ let typeDef = reader.GetTypeDefinition handle
+ let name = reader.GetString typeDef.Name
+
+ let visibility = typeDef.Attributes &&& TypeAttributes.VisibilityMask
+
+ let isNested =
+ match visibility with
+ | TypeAttributes.NestedPublic
+ | TypeAttributes.NestedPrivate
+ | TypeAttributes.NestedFamily
+ | TypeAttributes.NestedAssembly
+ | TypeAttributes.NestedFamORAssem
+ | TypeAttributes.NestedFamANDAssem -> true
+ | _ -> false
+
+ if isNested then
+ let declaringTypeHandle = typeDef.GetDeclaringType()
+ $"{buildName declaringTypeHandle}+{name}"
+ else
+ let namespaceName =
+ if typeDef.Namespace.IsNil then
+ ""
+ else
+ reader.GetString typeDef.Namespace
+
+ if String.IsNullOrEmpty namespaceName then
+ name
+ else
+ $"{namespaceName}.{name}"
+
+ buildName handle
+
+ let private emittedGeneratedNames assemblyPath =
+ use stream = File.OpenRead assemblyPath
+ use peReader = new PEReader(stream)
+ let reader = peReader.GetMetadataReader()
+
+ let names =
+ [ for typeHandle in reader.TypeDefinitions do
+ yield typeName reader typeHandle
+
+ let typeDef = reader.GetTypeDefinition typeHandle
+
+ for methodHandle in typeDef.GetMethods() do
+ let methodDef = reader.GetMethodDefinition methodHandle
+ yield reader.GetString methodDef.Name ]
+
+ names
+ |> List.filter (fun name -> name.IndexOf('@') >= 0)
+ |> List.sort
+
+ []
+ let ``normal compilation emits identical generated names across two compiles`` () =
+ let tempRoot =
+ Path.Combine(Path.GetTempPath(), "fsharp-generated-name-determinism-" + Guid.NewGuid().ToString("N"))
+
+ try
+ let firstOutput = Path.Combine(tempRoot, "first")
+ let secondOutput = Path.Combine(tempRoot, "second")
+
+ let firstNames = compileLibrary firstOutput |> emittedGeneratedNames
+ let secondNames = compileLibrary secondOutput |> emittedGeneratedNames
+
+ Assert.True(not firstNames.IsEmpty, "Expected at least one compiler-generated name in emitted metadata.")
+ Assert.DoesNotContain(firstNames, fun name -> name.Contains("@hotreload"))
+ Assert.Equal(firstNames, secondNames)
+ finally
+ if Directory.Exists tempRoot then
+ Directory.Delete(tempRoot, true)
diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateCustomType.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateCustomType.fs
new file mode 100644
index 00000000000..b0d991a9b7f
--- /dev/null
+++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateCustomType.fs
@@ -0,0 +1,42 @@
+module DelegateCustomType
+
+open System
+
+// Custom, F#-declared delegate types exercise construction with delegates defined in the *compiled* assembly
+// (local scope, unlike imported BCL Func/Action) and with Invoke signatures the Func/Action tests do not
+// cover: a multi-argument (tupled) signature, a generic delegate, and a byref parameter. (F# forbids curried
+// delegate signatures — FS0950 — so every F# delegate has a single tupled Invoke parameter group.)
+
+type DTupled = delegate of int * int -> int
+type DGen<'T> = delegate of 'T -> 'T
+type DByref = delegate of byref -> unit
+
+let acc (x: int) (y: int) : int = x + y
+
+let ident (x: 'T) : 'T = x
+
+type C() =
+ member _.M (x: int) (y: int) : int = x * y
+
+// Tupled-signature custom delegate: Invoke(int, int).
+// 28. non-eta module function, custom delegate
+let tupledNonEta () = DTupled(acc)
+// 14. eta module function, custom delegate
+let tupledEta () = DTupled(fun a b -> acc a b)
+
+// Instance member through a custom delegate: the receiver becomes the delegate's Target.
+// 29. non-eta instance member, custom delegate
+let instanceNonEta (c: C) = DTupled(c.M)
+// 15. eta instance member, custom delegate
+let instanceEta (c: C) = DTupled(fun a b -> c.M a b)
+
+// Generic custom delegate instantiated at int: Invoke(int):int over the generic target.
+// 30. non-eta generic method, generic custom delegate
+let genNonEta () = DGen(ident)
+// 16. eta generic method, generic custom delegate
+let genEta () = DGen(fun x -> ident x)
+
+// byref-parameter custom delegate: the body mutates through the byref, so it is not a transparent forwarding
+// call and stays a closure. Documents that a byref Invoke parameter does not break the recognizer.
+// 53. byref-parameter delegate (mutating body)
+let byrefMutate () = DByref(fun x -> x <- x + 1)
diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateCustomType.fs.OptimizeOff.Preview.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateCustomType.fs.OptimizeOff.Preview.il.bsl
new file mode 100644
index 00000000000..c9d56b7d0a0
--- /dev/null
+++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateCustomType.fs.OptimizeOff.Preview.il.bsl
@@ -0,0 +1,348 @@
+
+
+
+
+
+.assembly extern runtime { }
+.assembly extern FSharp.Core { }
+.assembly assembly
+{
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.FSharpInterfaceDataVersionAttribute::.ctor(int32,
+ int32,
+ int32) = ( 01 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 )
+
+
+
+
+ .hash algorithm 0x00008004
+ .ver 0:0:0:0
+}
+.module assembly.exe
+
+.imagebase {value}
+.file alignment 0x00000200
+.stackreserve 0x00100000
+.subsystem 0x0003
+.corflags 0x00000001
+
+
+
+
+
+.class public abstract auto ansi sealed assembly
+ extends [runtime]System.Object
+{
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 07 00 00 00 00 00 )
+ .class auto ansi serializable sealed nested public DTupled
+ extends [runtime]System.MulticastDelegate
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 )
+ .method public hidebysig specialname rtspecialname instance void .ctor(object 'object', native int 'method') runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual instance int32 Invoke(int32 A_1, int32 A_2) runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual
+ instance class [runtime]System.IAsyncResult
+ BeginInvoke(int32 A_1,
+ int32 A_2,
+ class [runtime]System.AsyncCallback callback,
+ object objects) runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual instance int32 EndInvoke(class [runtime]System.IAsyncResult result) runtime managed
+ {
+ }
+
+ }
+
+ .class auto ansi serializable sealed nested public DGen`1
+ extends [runtime]System.MulticastDelegate
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 )
+ .method public hidebysig specialname rtspecialname instance void .ctor(object 'object', native int 'method') runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual instance !T Invoke(!T A_1) runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual
+ instance class [runtime]System.IAsyncResult
+ BeginInvoke(!T A_1,
+ class [runtime]System.AsyncCallback callback,
+ object objects) runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual instance !T EndInvoke(class [runtime]System.IAsyncResult result) runtime managed
+ {
+ }
+
+ }
+
+ .class auto ansi serializable sealed nested public DByref
+ extends [runtime]System.MulticastDelegate
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 )
+ .method public hidebysig specialname rtspecialname instance void .ctor(object 'object', native int 'method') runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual instance void Invoke(int32& A_1) runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual
+ instance class [runtime]System.IAsyncResult
+ BeginInvoke(int32& A_1,
+ class [runtime]System.AsyncCallback callback,
+ object objects) runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual instance void EndInvoke(class [runtime]System.IAsyncResult result) runtime managed
+ {
+ }
+
+ }
+
+ .class auto ansi serializable nested public C
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 )
+ .method public specialname rtspecialname instance void .ctor() cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: callvirt instance void [runtime]System.Object::.ctor()
+ IL_0006: ldarg.0
+ IL_0007: pop
+ IL_0008: ret
+ }
+
+ .method public hidebysig instance int32 M(int32 x, int32 y) cil managed
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationArgumentCountsAttribute::.ctor(int32[]) = ( 01 00 02 00 00 00 01 00 00 00 01 00 00 00 00 00 )
+
+ .maxstack 8
+ IL_0000: ldarg.1
+ IL_0001: ldarg.2
+ IL_0002: mul
+ IL_0003: ret
+ }
+
+ }
+
+ .class abstract auto autochar serializable sealed nested assembly beforefieldinit specialname tupledEta@25
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 06 00 00 00 00 00 )
+ .method assembly static int32 Invoke(int32 a,
+ int32 b) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldarg.1
+ IL_0002: call int32 assembly::acc(int32,
+ int32)
+ IL_0007: ret
+ }
+
+ }
+
+ .class auto autochar serializable sealed nested assembly beforefieldinit specialname instanceEta@31
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 06 00 00 00 00 00 )
+ .field public class assembly/C c
+ .method public specialname rtspecialname instance void .ctor(class assembly/C c) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldarg.1
+ IL_0002: stfld class assembly/C assembly/instanceEta@31::c
+ IL_0007: ldarg.0
+ IL_0008: call instance void [runtime]System.Object::.ctor()
+ IL_000d: ret
+ }
+
+ .method assembly hidebysig instance int32 Invoke(int32 a, int32 b) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldfld class assembly/C assembly/instanceEta@31::c
+ IL_0006: ldarg.1
+ IL_0007: ldarg.2
+ IL_0008: callvirt instance int32 assembly/C::M(int32,
+ int32)
+ IL_000d: ret
+ }
+
+ }
+
+ .class abstract auto autochar serializable sealed nested assembly beforefieldinit specialname genEta@37
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 06 00 00 00 00 00 )
+ .method assembly static int32 Invoke(int32 x) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: call !!0 assembly::ident(!!0)
+ IL_0006: ret
+ }
+
+ }
+
+ .class abstract auto autochar serializable sealed nested assembly beforefieldinit specialname byrefMutate@42
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 06 00 00 00 00 00 )
+ .method assembly static void Invoke(int32& x) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldarg.0
+ IL_0002: ldobj [runtime]System.Int32
+ IL_0007: ldc.i4.1
+ IL_0008: add
+ IL_0009: stobj [runtime]System.Int32
+ IL_000e: ret
+ }
+
+ }
+
+ .method public static int32 acc(int32 x,
+ int32 y) cil managed
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationArgumentCountsAttribute::.ctor(int32[]) = ( 01 00 02 00 00 00 01 00 00 00 01 00 00 00 00 00 )
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldarg.1
+ IL_0002: add
+ IL_0003: ret
+ }
+
+ .method public static !!T ident(!!T x) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ret
+ }
+
+ .method public static class assembly/DTupled tupledNonEta() cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldnull
+ IL_0001: ldftn int32 assembly::acc(int32,
+ int32)
+ IL_0007: newobj instance void assembly/DTupled::.ctor(object,
+ native int)
+ IL_000c: ret
+ }
+
+ .method public static class assembly/DTupled tupledEta() cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldnull
+ IL_0001: ldftn int32 assembly/tupledEta@25::Invoke(int32,
+ int32)
+ IL_0007: newobj instance void assembly/DTupled::.ctor(object,
+ native int)
+ IL_000c: ret
+ }
+
+ .method public static class assembly/DTupled instanceNonEta(class assembly/C c) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldftn instance int32 assembly/C::M(int32,
+ int32)
+ IL_0007: newobj instance void assembly/DTupled::.ctor(object,
+ native int)
+ IL_000c: ret
+ }
+
+ .method public static class assembly/DTupled instanceEta(class assembly/C c) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: newobj instance void assembly/instanceEta@31::.ctor(class assembly/C)
+ IL_0006: ldftn instance int32 assembly/instanceEta@31::Invoke(int32,
+ int32)
+ IL_000c: newobj instance void assembly/DTupled::.ctor(object,
+ native int)
+ IL_0011: ret
+ }
+
+ .method public static class assembly/DGen`1 genNonEta() cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldnull
+ IL_0001: ldftn !!0 assembly::ident(!!0)
+ IL_0007: newobj instance void class assembly/DGen`1::.ctor(object,
+ native int)
+ IL_000c: ret
+ }
+
+ .method public static class assembly/DGen`1 genEta() cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldnull
+ IL_0001: ldftn int32 assembly/genEta@37::Invoke(int32)
+ IL_0007: newobj instance void class assembly/DGen`1::.ctor(object,
+ native int)
+ IL_000c: ret
+ }
+
+ .method public static class assembly/DByref byrefMutate() cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldnull
+ IL_0001: ldftn void assembly/byrefMutate@42::Invoke(int32&)
+ IL_0007: newobj instance void assembly/DByref::.ctor(object,
+ native int)
+ IL_000c: ret
+ }
+
+}
+
+.class private abstract auto ansi sealed ''.$assembly
+ extends [runtime]System.Object
+{
+ .method public static void main@() cil managed
+ {
+ .entrypoint
+
+ .maxstack 8
+ IL_0000: ret
+ }
+
+}
+
+
+
+
+
+
diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateCustomType.fs.OptimizeOff.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateCustomType.fs.OptimizeOff.il.bsl
new file mode 100644
index 00000000000..66053083940
--- /dev/null
+++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateCustomType.fs.OptimizeOff.il.bsl
@@ -0,0 +1,414 @@
+
+
+
+
+
+.assembly extern runtime { }
+.assembly extern FSharp.Core { }
+.assembly assembly
+{
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.FSharpInterfaceDataVersionAttribute::.ctor(int32,
+ int32,
+ int32) = ( 01 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 )
+
+
+
+
+ .hash algorithm 0x00008004
+ .ver 0:0:0:0
+}
+.module assembly.exe
+
+.imagebase {value}
+.file alignment 0x00000200
+.stackreserve 0x00100000
+.subsystem 0x0003
+.corflags 0x00000001
+
+
+
+
+
+.class public abstract auto ansi sealed assembly
+ extends [runtime]System.Object
+{
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 07 00 00 00 00 00 )
+ .class auto ansi serializable sealed nested public DTupled
+ extends [runtime]System.MulticastDelegate
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 )
+ .method public hidebysig specialname rtspecialname instance void .ctor(object 'object', native int 'method') runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual instance int32 Invoke(int32 A_1, int32 A_2) runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual
+ instance class [runtime]System.IAsyncResult
+ BeginInvoke(int32 A_1,
+ int32 A_2,
+ class [runtime]System.AsyncCallback callback,
+ object objects) runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual instance int32 EndInvoke(class [runtime]System.IAsyncResult result) runtime managed
+ {
+ }
+
+ }
+
+ .class auto ansi serializable sealed nested public DGen`1
+ extends [runtime]System.MulticastDelegate
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 )
+ .method public hidebysig specialname rtspecialname instance void .ctor(object 'object', native int 'method') runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual instance !T Invoke(!T A_1) runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual
+ instance class [runtime]System.IAsyncResult
+ BeginInvoke(!T A_1,
+ class [runtime]System.AsyncCallback callback,
+ object objects) runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual instance !T EndInvoke(class [runtime]System.IAsyncResult result) runtime managed
+ {
+ }
+
+ }
+
+ .class auto ansi serializable sealed nested public DByref
+ extends [runtime]System.MulticastDelegate
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 )
+ .method public hidebysig specialname rtspecialname instance void .ctor(object 'object', native int 'method') runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual instance void Invoke(int32& A_1) runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual
+ instance class [runtime]System.IAsyncResult
+ BeginInvoke(int32& A_1,
+ class [runtime]System.AsyncCallback callback,
+ object objects) runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual instance void EndInvoke(class [runtime]System.IAsyncResult result) runtime managed
+ {
+ }
+
+ }
+
+ .class auto ansi serializable nested public C
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 )
+ .method public specialname rtspecialname instance void .ctor() cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: callvirt instance void [runtime]System.Object::.ctor()
+ IL_0006: ldarg.0
+ IL_0007: pop
+ IL_0008: ret
+ }
+
+ .method public hidebysig instance int32 M(int32 x, int32 y) cil managed
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationArgumentCountsAttribute::.ctor(int32[]) = ( 01 00 02 00 00 00 01 00 00 00 01 00 00 00 00 00 )
+
+ .maxstack 8
+ IL_0000: ldarg.1
+ IL_0001: ldarg.2
+ IL_0002: mul
+ IL_0003: ret
+ }
+
+ }
+
+ .class abstract auto autochar serializable sealed nested assembly beforefieldinit specialname tupledNonEta@23
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 06 00 00 00 00 00 )
+ .method assembly static int32 Invoke(int32 x,
+ int32 y) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldarg.1
+ IL_0002: call int32 assembly::acc(int32,
+ int32)
+ IL_0007: ret
+ }
+
+ }
+
+ .class abstract auto autochar serializable sealed nested assembly beforefieldinit specialname tupledEta@25
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 06 00 00 00 00 00 )
+ .method assembly static int32 Invoke(int32 a,
+ int32 b) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldarg.1
+ IL_0002: call int32 assembly::acc(int32,
+ int32)
+ IL_0007: ret
+ }
+
+ }
+
+ .class auto autochar serializable sealed nested assembly beforefieldinit specialname instanceNonEta@29
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 06 00 00 00 00 00 )
+ .field public class assembly/C c
+ .method public specialname rtspecialname instance void .ctor(class assembly/C c) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldarg.1
+ IL_0002: stfld class assembly/C assembly/instanceNonEta@29::c
+ IL_0007: ldarg.0
+ IL_0008: call instance void [runtime]System.Object::.ctor()
+ IL_000d: ret
+ }
+
+ .method assembly hidebysig instance int32 Invoke(int32 delegateArg0, int32 delegateArg1) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldfld class assembly/C assembly/instanceNonEta@29::c
+ IL_0006: ldarg.1
+ IL_0007: ldarg.2
+ IL_0008: callvirt instance int32 assembly/C::M(int32,
+ int32)
+ IL_000d: ret
+ }
+
+ }
+
+ .class auto autochar serializable sealed nested assembly beforefieldinit specialname instanceEta@31
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 06 00 00 00 00 00 )
+ .field public class assembly/C c
+ .method public specialname rtspecialname instance void .ctor(class assembly/C c) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldarg.1
+ IL_0002: stfld class assembly/C assembly/instanceEta@31::c
+ IL_0007: ldarg.0
+ IL_0008: call instance void [runtime]System.Object::.ctor()
+ IL_000d: ret
+ }
+
+ .method assembly hidebysig instance int32 Invoke(int32 a, int32 b) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldfld class assembly/C assembly/instanceEta@31::c
+ IL_0006: ldarg.1
+ IL_0007: ldarg.2
+ IL_0008: callvirt instance int32 assembly/C::M(int32,
+ int32)
+ IL_000d: ret
+ }
+
+ }
+
+ .class abstract auto autochar serializable sealed nested assembly beforefieldinit specialname genNonEta@35
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 06 00 00 00 00 00 )
+ .method assembly static int32 Invoke(int32 delegateArg0) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: call !!0 assembly::ident(!!0)
+ IL_0006: ret
+ }
+
+ }
+
+ .class abstract auto autochar serializable sealed nested assembly beforefieldinit specialname genEta@37
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 06 00 00 00 00 00 )
+ .method assembly static int32 Invoke(int32 x) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: call !!0 assembly::ident(!!0)
+ IL_0006: ret
+ }
+
+ }
+
+ .class abstract auto autochar serializable sealed nested assembly beforefieldinit specialname byrefMutate@42
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 06 00 00 00 00 00 )
+ .method assembly static void Invoke(int32& x) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldarg.0
+ IL_0002: ldobj [runtime]System.Int32
+ IL_0007: ldc.i4.1
+ IL_0008: add
+ IL_0009: stobj [runtime]System.Int32
+ IL_000e: ret
+ }
+
+ }
+
+ .method public static int32 acc(int32 x,
+ int32 y) cil managed
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationArgumentCountsAttribute::.ctor(int32[]) = ( 01 00 02 00 00 00 01 00 00 00 01 00 00 00 00 00 )
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldarg.1
+ IL_0002: add
+ IL_0003: ret
+ }
+
+ .method public static !!T ident(!!T x) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ret
+ }
+
+ .method public static class assembly/DTupled tupledNonEta() cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldnull
+ IL_0001: ldftn int32 assembly/tupledNonEta@23::Invoke(int32,
+ int32)
+ IL_0007: newobj instance void assembly/DTupled::.ctor(object,
+ native int)
+ IL_000c: ret
+ }
+
+ .method public static class assembly/DTupled tupledEta() cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldnull
+ IL_0001: ldftn int32 assembly/tupledEta@25::Invoke(int32,
+ int32)
+ IL_0007: newobj instance void assembly/DTupled::.ctor(object,
+ native int)
+ IL_000c: ret
+ }
+
+ .method public static class assembly/DTupled instanceNonEta(class assembly/C c) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: newobj instance void assembly/instanceNonEta@29::.ctor(class assembly/C)
+ IL_0006: ldftn instance int32 assembly/instanceNonEta@29::Invoke(int32,
+ int32)
+ IL_000c: newobj instance void assembly/DTupled::.ctor(object,
+ native int)
+ IL_0011: ret
+ }
+
+ .method public static class assembly/DTupled instanceEta(class assembly/C c) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: newobj instance void assembly/instanceEta@31::.ctor(class assembly/C)
+ IL_0006: ldftn instance int32 assembly/instanceEta@31::Invoke(int32,
+ int32)
+ IL_000c: newobj instance void assembly/DTupled::.ctor(object,
+ native int)
+ IL_0011: ret
+ }
+
+ .method public static class assembly/DGen`1 genNonEta() cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldnull
+ IL_0001: ldftn int32 assembly/genNonEta@35::Invoke(int32)
+ IL_0007: newobj instance void class assembly/DGen`1::.ctor(object,
+ native int)
+ IL_000c: ret
+ }
+
+ .method public static class assembly/DGen`1 genEta() cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldnull
+ IL_0001: ldftn int32 assembly/genEta@37::Invoke(int32)
+ IL_0007: newobj instance void class assembly/DGen`1::.ctor(object,
+ native int)
+ IL_000c: ret
+ }
+
+ .method public static class assembly/DByref byrefMutate() cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldnull
+ IL_0001: ldftn void assembly/byrefMutate@42::Invoke(int32&)
+ IL_0007: newobj instance void assembly/DByref::.ctor(object,
+ native int)
+ IL_000c: ret
+ }
+
+}
+
+.class private abstract auto ansi sealed ''.$assembly
+ extends [runtime]System.Object
+{
+ .method public static void main@() cil managed
+ {
+ .entrypoint
+
+ .maxstack 8
+ IL_0000: ret
+ }
+
+}
+
+
+
+
+
+
diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateCustomType.fs.OptimizeOn.Preview.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateCustomType.fs.OptimizeOn.Preview.il.bsl
new file mode 100644
index 00000000000..15a1a5f9f34
--- /dev/null
+++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateCustomType.fs.OptimizeOn.Preview.il.bsl
@@ -0,0 +1,282 @@
+
+
+
+
+
+.assembly extern runtime { }
+.assembly extern FSharp.Core { }
+.assembly assembly
+{
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.FSharpInterfaceDataVersionAttribute::.ctor(int32,
+ int32,
+ int32) = ( 01 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 )
+
+
+
+
+ .hash algorithm 0x00008004
+ .ver 0:0:0:0
+}
+.module assembly.exe
+
+.imagebase {value}
+.file alignment 0x00000200
+.stackreserve 0x00100000
+.subsystem 0x0003
+.corflags 0x00000001
+
+
+
+
+
+.class public abstract auto ansi sealed assembly
+ extends [runtime]System.Object
+{
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 07 00 00 00 00 00 )
+ .class auto ansi serializable sealed nested public DTupled
+ extends [runtime]System.MulticastDelegate
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 )
+ .method public hidebysig specialname rtspecialname instance void .ctor(object 'object', native int 'method') runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual instance int32 Invoke(int32 A_1, int32 A_2) runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual
+ instance class [runtime]System.IAsyncResult
+ BeginInvoke(int32 A_1,
+ int32 A_2,
+ class [runtime]System.AsyncCallback callback,
+ object objects) runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual instance int32 EndInvoke(class [runtime]System.IAsyncResult result) runtime managed
+ {
+ }
+
+ }
+
+ .class auto ansi serializable sealed nested public DGen`1
+ extends [runtime]System.MulticastDelegate
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 )
+ .method public hidebysig specialname rtspecialname instance void .ctor(object 'object', native int 'method') runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual instance !T Invoke(!T A_1) runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual
+ instance class [runtime]System.IAsyncResult
+ BeginInvoke(!T A_1,
+ class [runtime]System.AsyncCallback callback,
+ object objects) runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual instance !T EndInvoke(class [runtime]System.IAsyncResult result) runtime managed
+ {
+ }
+
+ }
+
+ .class auto ansi serializable sealed nested public DByref
+ extends [runtime]System.MulticastDelegate
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 )
+ .method public hidebysig specialname rtspecialname instance void .ctor(object 'object', native int 'method') runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual instance void Invoke(int32& A_1) runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual
+ instance class [runtime]System.IAsyncResult
+ BeginInvoke(int32& A_1,
+ class [runtime]System.AsyncCallback callback,
+ object objects) runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual instance void EndInvoke(class [runtime]System.IAsyncResult result) runtime managed
+ {
+ }
+
+ }
+
+ .class auto ansi serializable nested public C
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 )
+ .method public specialname rtspecialname instance void .ctor() cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: callvirt instance void [runtime]System.Object::.ctor()
+ IL_0006: ldarg.0
+ IL_0007: pop
+ IL_0008: ret
+ }
+
+ .method public hidebysig instance int32 M(int32 x, int32 y) cil managed
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationArgumentCountsAttribute::.ctor(int32[]) = ( 01 00 02 00 00 00 01 00 00 00 01 00 00 00 00 00 )
+
+ .maxstack 8
+ IL_0000: ldarg.1
+ IL_0001: ldarg.2
+ IL_0002: mul
+ IL_0003: ret
+ }
+
+ }
+
+ .class abstract auto autochar serializable sealed nested assembly beforefieldinit specialname byrefMutate@42
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 06 00 00 00 00 00 )
+ .method assembly static void Invoke(int32& x) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldarg.0
+ IL_0002: ldobj [runtime]System.Int32
+ IL_0007: ldc.i4.1
+ IL_0008: add
+ IL_0009: stobj [runtime]System.Int32
+ IL_000e: ret
+ }
+
+ }
+
+ .method public static int32 acc(int32 x,
+ int32 y) cil managed
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationArgumentCountsAttribute::.ctor(int32[]) = ( 01 00 02 00 00 00 01 00 00 00 01 00 00 00 00 00 )
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldarg.1
+ IL_0002: add
+ IL_0003: ret
+ }
+
+ .method public static !!T ident(!!T x) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ret
+ }
+
+ .method public static class assembly/DTupled tupledNonEta() cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldnull
+ IL_0001: ldftn int32 assembly::acc(int32,
+ int32)
+ IL_0007: newobj instance void assembly/DTupled::.ctor(object,
+ native int)
+ IL_000c: ret
+ }
+
+ .method public static class assembly/DTupled tupledEta() cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldnull
+ IL_0001: ldftn int32 assembly::acc(int32,
+ int32)
+ IL_0007: newobj instance void assembly/DTupled::.ctor(object,
+ native int)
+ IL_000c: ret
+ }
+
+ .method public static class assembly/DTupled instanceNonEta(class assembly/C c) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldftn instance int32 assembly/C::M(int32,
+ int32)
+ IL_0007: newobj instance void assembly/DTupled::.ctor(object,
+ native int)
+ IL_000c: ret
+ }
+
+ .method public static class assembly/DTupled instanceEta(class assembly/C c) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldftn instance int32 assembly/C::M(int32,
+ int32)
+ IL_0007: newobj instance void assembly/DTupled::.ctor(object,
+ native int)
+ IL_000c: ret
+ }
+
+ .method public static class assembly/DGen`1 genNonEta() cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldnull
+ IL_0001: ldftn !!0 assembly::ident(!!0)
+ IL_0007: newobj instance void class assembly/DGen`1::.ctor(object,
+ native int)
+ IL_000c: ret
+ }
+
+ .method public static class assembly/DGen`1 genEta() cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldnull
+ IL_0001: ldftn !!0 assembly::ident(!!0)
+ IL_0007: newobj instance void class assembly/DGen`1::.ctor(object,
+ native int)
+ IL_000c: ret
+ }
+
+ .method public static class assembly/DByref byrefMutate() cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldnull
+ IL_0001: ldftn void assembly/byrefMutate@42::Invoke(int32&)
+ IL_0007: newobj instance void assembly/DByref::.ctor(object,
+ native int)
+ IL_000c: ret
+ }
+
+}
+
+.class private abstract auto ansi sealed ''.$assembly
+ extends [runtime]System.Object
+{
+ .method public static void main@() cil managed
+ {
+ .entrypoint
+
+ .maxstack 8
+ IL_0000: ret
+ }
+
+}
+
+
+
+
+
+
diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateCustomType.fs.OptimizeOn.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateCustomType.fs.OptimizeOn.il.bsl
new file mode 100644
index 00000000000..91f76166944
--- /dev/null
+++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateCustomType.fs.OptimizeOn.il.bsl
@@ -0,0 +1,378 @@
+
+
+
+
+
+.assembly extern runtime { }
+.assembly extern FSharp.Core { }
+.assembly assembly
+{
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.FSharpInterfaceDataVersionAttribute::.ctor(int32,
+ int32,
+ int32) = ( 01 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 )
+
+
+
+
+ .hash algorithm 0x00008004
+ .ver 0:0:0:0
+}
+.module assembly.exe
+
+.imagebase {value}
+.file alignment 0x00000200
+.stackreserve 0x00100000
+.subsystem 0x0003
+.corflags 0x00000001
+
+
+
+
+
+.class public abstract auto ansi sealed assembly
+ extends [runtime]System.Object
+{
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 07 00 00 00 00 00 )
+ .class auto ansi serializable sealed nested public DTupled
+ extends [runtime]System.MulticastDelegate
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 )
+ .method public hidebysig specialname rtspecialname instance void .ctor(object 'object', native int 'method') runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual instance int32 Invoke(int32 A_1, int32 A_2) runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual
+ instance class [runtime]System.IAsyncResult
+ BeginInvoke(int32 A_1,
+ int32 A_2,
+ class [runtime]System.AsyncCallback callback,
+ object objects) runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual instance int32 EndInvoke(class [runtime]System.IAsyncResult result) runtime managed
+ {
+ }
+
+ }
+
+ .class auto ansi serializable sealed nested public DGen`1
+ extends [runtime]System.MulticastDelegate
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 )
+ .method public hidebysig specialname rtspecialname instance void .ctor(object 'object', native int 'method') runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual instance !T Invoke(!T A_1) runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual
+ instance class [runtime]System.IAsyncResult
+ BeginInvoke(!T A_1,
+ class [runtime]System.AsyncCallback callback,
+ object objects) runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual instance !T EndInvoke(class [runtime]System.IAsyncResult result) runtime managed
+ {
+ }
+
+ }
+
+ .class auto ansi serializable sealed nested public DByref
+ extends [runtime]System.MulticastDelegate
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 )
+ .method public hidebysig specialname rtspecialname instance void .ctor(object 'object', native int 'method') runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual instance void Invoke(int32& A_1) runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual
+ instance class [runtime]System.IAsyncResult
+ BeginInvoke(int32& A_1,
+ class [runtime]System.AsyncCallback callback,
+ object objects) runtime managed
+ {
+ }
+
+ .method public hidebysig strict virtual instance void EndInvoke(class [runtime]System.IAsyncResult result) runtime managed
+ {
+ }
+
+ }
+
+ .class auto ansi serializable nested public C
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 )
+ .method public specialname rtspecialname instance void .ctor() cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: callvirt instance void [runtime]System.Object::.ctor()
+ IL_0006: ldarg.0
+ IL_0007: pop
+ IL_0008: ret
+ }
+
+ .method public hidebysig instance int32 M(int32 x, int32 y) cil managed
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationArgumentCountsAttribute::.ctor(int32[]) = ( 01 00 02 00 00 00 01 00 00 00 01 00 00 00 00 00 )
+
+ .maxstack 8
+ IL_0000: ldarg.1
+ IL_0001: ldarg.2
+ IL_0002: mul
+ IL_0003: ret
+ }
+
+ }
+
+ .class abstract auto autochar serializable sealed nested assembly beforefieldinit specialname tupledNonEta@23
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 06 00 00 00 00 00 )
+ .method assembly static int32 Invoke(int32 x,
+ int32 y) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldarg.1
+ IL_0002: add
+ IL_0003: ret
+ }
+
+ }
+
+ .class abstract auto autochar serializable sealed nested assembly beforefieldinit specialname tupledEta@25
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 06 00 00 00 00 00 )
+ .method assembly static int32 Invoke(int32 a,
+ int32 b) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldarg.1
+ IL_0002: add
+ IL_0003: ret
+ }
+
+ }
+
+ .class abstract auto autochar serializable sealed nested assembly beforefieldinit specialname instanceNonEta@29
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 06 00 00 00 00 00 )
+ .method assembly static int32 Invoke(int32 delegateArg0,
+ int32 delegateArg1) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldarg.1
+ IL_0002: mul
+ IL_0003: ret
+ }
+
+ }
+
+ .class abstract auto autochar serializable sealed nested assembly beforefieldinit specialname instanceEta@31
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 06 00 00 00 00 00 )
+ .method assembly static int32 Invoke(int32 a,
+ int32 b) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldarg.1
+ IL_0002: mul
+ IL_0003: ret
+ }
+
+ }
+
+ .class abstract auto autochar serializable sealed nested assembly beforefieldinit specialname genNonEta@35
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 06 00 00 00 00 00 )
+ .method assembly static int32 Invoke(int32 delegateArg0) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ret
+ }
+
+ }
+
+ .class abstract auto autochar serializable sealed nested assembly beforefieldinit specialname genEta@37
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 06 00 00 00 00 00 )
+ .method assembly static int32 Invoke(int32 x) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ret
+ }
+
+ }
+
+ .class abstract auto autochar serializable sealed nested assembly beforefieldinit specialname byrefMutate@42
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 06 00 00 00 00 00 )
+ .method assembly static void Invoke(int32& x) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldarg.0
+ IL_0002: ldobj [runtime]System.Int32
+ IL_0007: ldc.i4.1
+ IL_0008: add
+ IL_0009: stobj [runtime]System.Int32
+ IL_000e: ret
+ }
+
+ }
+
+ .method public static int32 acc(int32 x,
+ int32 y) cil managed
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationArgumentCountsAttribute::.ctor(int32[]) = ( 01 00 02 00 00 00 01 00 00 00 01 00 00 00 00 00 )
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldarg.1
+ IL_0002: add
+ IL_0003: ret
+ }
+
+ .method public static !!T ident(!!T x) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ret
+ }
+
+ .method public static class assembly/DTupled tupledNonEta() cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldnull
+ IL_0001: ldftn int32 assembly/tupledNonEta@23::Invoke(int32,
+ int32)
+ IL_0007: newobj instance void assembly/DTupled::.ctor(object,
+ native int)
+ IL_000c: ret
+ }
+
+ .method public static class assembly/DTupled tupledEta() cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldnull
+ IL_0001: ldftn int32 assembly/tupledEta@25::Invoke(int32,
+ int32)
+ IL_0007: newobj instance void assembly/DTupled::.ctor(object,
+ native int)
+ IL_000c: ret
+ }
+
+ .method public static class assembly/DTupled instanceNonEta(class assembly/C c) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldnull
+ IL_0001: ldftn int32 assembly/instanceNonEta@29::Invoke(int32,
+ int32)
+ IL_0007: newobj instance void assembly/DTupled::.ctor(object,
+ native int)
+ IL_000c: ret
+ }
+
+ .method public static class assembly/DTupled instanceEta(class assembly/C c) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldnull
+ IL_0001: ldftn int32 assembly/instanceEta@31::Invoke(int32,
+ int32)
+ IL_0007: newobj instance void assembly/DTupled::.ctor(object,
+ native int)
+ IL_000c: ret
+ }
+
+ .method public static class assembly/DGen`1 genNonEta() cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldnull
+ IL_0001: ldftn int32 assembly/genNonEta@35::Invoke(int32)
+ IL_0007: newobj instance void class assembly/DGen`1::.ctor(object,
+ native int)
+ IL_000c: ret
+ }
+
+ .method public static class assembly/DGen`1 genEta() cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldnull
+ IL_0001: ldftn int32 assembly/genEta@37::Invoke(int32)
+ IL_0007: newobj instance void class assembly/DGen`1::.ctor(object,
+ native int)
+ IL_000c: ret
+ }
+
+ .method public static class assembly/DByref byrefMutate() cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldnull
+ IL_0001: ldftn void assembly/byrefMutate@42::Invoke(int32&)
+ IL_0007: newobj instance void assembly/DByref::.ctor(object,
+ native int)
+ IL_000c: ret
+ }
+
+}
+
+.class private abstract auto ansi sealed ''.$assembly
+ extends [runtime]System.Object
+{
+ .method public static void main@() cil managed
+ {
+ .entrypoint
+
+ .maxstack 8
+ IL_0000: ret
+ }
+
+}
+
+
+
+
+
+
diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateExtensionMethod.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateExtensionMethod.fs
new file mode 100644
index 00000000000..6d7ecdd9960
--- /dev/null
+++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateExtensionMethod.fs
@@ -0,0 +1,22 @@
+module DelegateExtensionMethod
+
+open System
+open System.Runtime.CompilerServices
+
+type Holder() =
+ class
+ end
+
+[]
+type HolderExtensions =
+ []
+ static member Combine (h: Holder, x: int, y: int) : int = x + y
+
+// An extension member compiles to a static method whose first parameter is the receiver. Using it as a
+// delegate target binds that receiver as a leading argument, which the CLR's "closed over the first argument"
+// delegate stores as the Target while the function pointer points at the static method - a direct delegate.
+// (The member here is tupled, 'Combine(h, x, y)'; the recognizer de-tuples the forwarding call by the target's
+// arity, exactly as the code generator does when emitting the call.) As an eta-expanded delegate it is direct
+// only in optimized builds, where the user's lambda need not survive for debugging.
+// 52. extension member (receiver is a leading static arg, bound as Target)
+let extensionEta (h: Holder) = Func(fun a b -> h.Combine(a, b))
diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateExtensionMethod.fs.OptimizeOff.Preview.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateExtensionMethod.fs.OptimizeOff.Preview.il.bsl
new file mode 100644
index 00000000000..0e4b863938b
--- /dev/null
+++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateExtensionMethod.fs.OptimizeOff.Preview.il.bsl
@@ -0,0 +1,138 @@
+
+
+
+
+
+.assembly extern runtime { }
+.assembly extern FSharp.Core { }
+.assembly assembly
+{
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.FSharpInterfaceDataVersionAttribute::.ctor(int32,
+ int32,
+ int32) = ( 01 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 )
+
+
+
+
+ .hash algorithm 0x00008004
+ .ver 0:0:0:0
+}
+.module assembly.exe
+
+.imagebase {value}
+.file alignment 0x00000200
+.stackreserve 0x00100000
+.subsystem 0x0003
+.corflags 0x00000001
+
+
+
+
+
+.class public abstract auto ansi sealed assembly
+ extends [runtime]System.Object
+{
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 07 00 00 00 00 00 )
+ .class auto ansi serializable nested public Holder
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 )
+ .method public specialname rtspecialname instance void .ctor() cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: callvirt instance void [runtime]System.Object::.ctor()
+ IL_0006: ldarg.0
+ IL_0007: pop
+ IL_0008: ret
+ }
+
+ }
+
+ .class auto ansi serializable nested public HolderExtensions
+ extends [runtime]System.Object
+ {
+ .custom instance void [runtime]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 )
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 )
+ .method public static int32 Combine(class assembly/Holder h,
+ int32 x,
+ int32 y) cil managed
+ {
+ .custom instance void [runtime]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 )
+
+ .maxstack 8
+ IL_0000: ldarg.1
+ IL_0001: ldarg.2
+ IL_0002: add
+ IL_0003: ret
+ }
+
+ }
+
+ .class auto autochar serializable sealed nested assembly beforefieldinit specialname extensionEta@22
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 06 00 00 00 00 00 )
+ .field public class assembly/Holder h
+ .method public specialname rtspecialname instance void .ctor(class assembly/Holder h) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldarg.1
+ IL_0002: stfld class assembly/Holder assembly/extensionEta@22::h
+ IL_0007: ldarg.0
+ IL_0008: call instance void [runtime]System.Object::.ctor()
+ IL_000d: ret
+ }
+
+ .method assembly hidebysig instance int32 Invoke(int32 a, int32 b) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldfld class assembly/Holder assembly/extensionEta@22::h
+ IL_0006: ldarg.1
+ IL_0007: ldarg.2
+ IL_0008: call int32 assembly/HolderExtensions::Combine(class assembly/Holder,
+ int32,
+ int32)
+ IL_000d: ret
+ }
+
+ }
+
+ .method public static class [runtime]System.Func`3 extensionEta(class assembly/Holder h) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: newobj instance void assembly/extensionEta@22::.ctor(class assembly/Holder)
+ IL_0006: ldftn instance int32 assembly/extensionEta@22::Invoke(int32,
+ int32)
+ IL_000c: newobj instance void class [runtime]System.Func`3::.ctor(object,
+ native int)
+ IL_0011: ret
+ }
+
+}
+
+.class private abstract auto ansi sealed ''.$assembly
+ extends [runtime]System.Object
+{
+ .method public static void main@() cil managed
+ {
+ .entrypoint
+
+ .maxstack 8
+ IL_0000: ret
+ }
+
+}
+
+
+
+
+
+
diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateExtensionMethod.fs.OptimizeOff.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateExtensionMethod.fs.OptimizeOff.il.bsl
new file mode 100644
index 00000000000..940811fcfab
--- /dev/null
+++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateExtensionMethod.fs.OptimizeOff.il.bsl
@@ -0,0 +1,138 @@
+
+
+
+
+
+.assembly extern runtime { }
+.assembly extern FSharp.Core { }
+.assembly assembly
+{
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.FSharpInterfaceDataVersionAttribute::.ctor(int32,
+ int32,
+ int32) = ( 01 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 )
+
+
+
+
+ .hash algorithm 0x00008004
+ .ver 0:0:0:0
+}
+.module assembly.exe
+
+.imagebase {value}
+.file alignment 0x00000200
+.stackreserve 0x00100000
+.subsystem 0x0003
+.corflags 0x00000001
+
+
+
+
+
+.class public abstract auto ansi sealed assembly
+ extends [runtime]System.Object
+{
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 07 00 00 00 00 00 )
+ .class auto ansi serializable nested public Holder
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 )
+ .method public specialname rtspecialname instance void .ctor() cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: callvirt instance void [runtime]System.Object::.ctor()
+ IL_0006: ldarg.0
+ IL_0007: pop
+ IL_0008: ret
+ }
+
+ }
+
+ .class auto ansi serializable nested public HolderExtensions
+ extends [runtime]System.Object
+ {
+ .custom instance void [runtime]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 )
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 )
+ .method public static int32 Combine(class assembly/Holder h,
+ int32 x,
+ int32 y) cil managed
+ {
+ .custom instance void [runtime]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 )
+
+ .maxstack 8
+ IL_0000: ldarg.1
+ IL_0001: ldarg.2
+ IL_0002: add
+ IL_0003: ret
+ }
+
+ }
+
+ .class auto autochar serializable sealed nested assembly beforefieldinit specialname extensionEta@22
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 06 00 00 00 00 00 )
+ .field public class assembly/Holder h
+ .method public specialname rtspecialname instance void .ctor(class assembly/Holder h) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldarg.1
+ IL_0002: stfld class assembly/Holder assembly/extensionEta@22::h
+ IL_0007: ldarg.0
+ IL_0008: call instance void [runtime]System.Object::.ctor()
+ IL_000d: ret
+ }
+
+ .method assembly hidebysig instance int32 Invoke(int32 a, int32 b) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldfld class assembly/Holder assembly/extensionEta@22::h
+ IL_0006: ldarg.1
+ IL_0007: ldarg.2
+ IL_0008: call int32 assembly/HolderExtensions::Combine(class assembly/Holder,
+ int32,
+ int32)
+ IL_000d: ret
+ }
+
+ }
+
+ .method public static class [runtime]System.Func`3 extensionEta(class assembly/Holder h) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: newobj instance void assembly/extensionEta@22::.ctor(class assembly/Holder)
+ IL_0006: ldftn instance int32 assembly/extensionEta@22::Invoke(int32,
+ int32)
+ IL_000c: newobj instance void class [runtime]System.Func`3::.ctor(object,
+ native int)
+ IL_0011: ret
+ }
+
+}
+
+.class private abstract auto ansi sealed ''.$assembly
+ extends [runtime]System.Object
+{
+ .method public static void main@() cil managed
+ {
+ .entrypoint
+
+ .maxstack 8
+ IL_0000: ret
+ }
+
+}
+
+
+
+
+
+
diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateExtensionMethod.fs.OptimizeOn.Preview.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateExtensionMethod.fs.OptimizeOn.Preview.il.bsl
new file mode 100644
index 00000000000..6e78d1cdc46
--- /dev/null
+++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateExtensionMethod.fs.OptimizeOn.Preview.il.bsl
@@ -0,0 +1,105 @@
+
+
+
+
+
+.assembly extern runtime { }
+.assembly extern FSharp.Core { }
+.assembly assembly
+{
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.FSharpInterfaceDataVersionAttribute::.ctor(int32,
+ int32,
+ int32) = ( 01 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 )
+
+
+
+
+ .hash algorithm 0x00008004
+ .ver 0:0:0:0
+}
+.module assembly.exe
+
+.imagebase {value}
+.file alignment 0x00000200
+.stackreserve 0x00100000
+.subsystem 0x0003
+.corflags 0x00000001
+
+
+
+
+
+.class public abstract auto ansi sealed assembly
+ extends [runtime]System.Object
+{
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 07 00 00 00 00 00 )
+ .class auto ansi serializable nested public Holder
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 )
+ .method public specialname rtspecialname instance void .ctor() cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: callvirt instance void [runtime]System.Object::.ctor()
+ IL_0006: ldarg.0
+ IL_0007: pop
+ IL_0008: ret
+ }
+
+ }
+
+ .class auto ansi serializable nested public HolderExtensions
+ extends [runtime]System.Object
+ {
+ .custom instance void [runtime]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 )
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 )
+ .method public static int32 Combine(class assembly/Holder h,
+ int32 x,
+ int32 y) cil managed
+ {
+ .custom instance void [runtime]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 )
+
+ .maxstack 8
+ IL_0000: ldarg.1
+ IL_0001: ldarg.2
+ IL_0002: add
+ IL_0003: ret
+ }
+
+ }
+
+ .method public static class [runtime]System.Func`3 extensionEta(class assembly/Holder h) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldftn int32 assembly/HolderExtensions::Combine(class assembly/Holder,
+ int32,
+ int32)
+ IL_0007: newobj instance void class [runtime]System.Func`3::.ctor(object,
+ native int)
+ IL_000c: ret
+ }
+
+}
+
+.class private abstract auto ansi sealed ''.$assembly
+ extends [runtime]System.Object
+{
+ .method public static void main@() cil managed
+ {
+ .entrypoint
+
+ .maxstack 8
+ IL_0000: ret
+ }
+
+}
+
+
+
+
+
+
diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateExtensionMethod.fs.OptimizeOn.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateExtensionMethod.fs.OptimizeOn.il.bsl
new file mode 100644
index 00000000000..8168284b0b3
--- /dev/null
+++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateExtensionMethod.fs.OptimizeOn.il.bsl
@@ -0,0 +1,121 @@
+
+
+
+
+
+.assembly extern runtime { }
+.assembly extern FSharp.Core { }
+.assembly assembly
+{
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.FSharpInterfaceDataVersionAttribute::.ctor(int32,
+ int32,
+ int32) = ( 01 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 )
+
+
+
+
+ .hash algorithm 0x00008004
+ .ver 0:0:0:0
+}
+.module assembly.exe
+
+.imagebase {value}
+.file alignment 0x00000200
+.stackreserve 0x00100000
+.subsystem 0x0003
+.corflags 0x00000001
+
+
+
+
+
+.class public abstract auto ansi sealed assembly
+ extends [runtime]System.Object
+{
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 07 00 00 00 00 00 )
+ .class auto ansi serializable nested public Holder
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 )
+ .method public specialname rtspecialname instance void .ctor() cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: callvirt instance void [runtime]System.Object::.ctor()
+ IL_0006: ldarg.0
+ IL_0007: pop
+ IL_0008: ret
+ }
+
+ }
+
+ .class auto ansi serializable nested public HolderExtensions
+ extends [runtime]System.Object
+ {
+ .custom instance void [runtime]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 )
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 )
+ .method public static int32 Combine(class assembly/Holder h,
+ int32 x,
+ int32 y) cil managed
+ {
+ .custom instance void [runtime]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 )
+
+ .maxstack 8
+ IL_0000: ldarg.1
+ IL_0001: ldarg.2
+ IL_0002: add
+ IL_0003: ret
+ }
+
+ }
+
+ .class abstract auto autochar serializable sealed nested assembly beforefieldinit specialname extensionEta@22
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 06 00 00 00 00 00 )
+ .method assembly static int32 Invoke(int32 a,
+ int32 b) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldarg.1
+ IL_0002: add
+ IL_0003: ret
+ }
+
+ }
+
+ .method public static class [runtime]System.Func`3 extensionEta(class assembly/Holder h) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldnull
+ IL_0001: ldftn int32 assembly/extensionEta@22::Invoke(int32,
+ int32)
+ IL_0007: newobj instance void class [runtime]System.Func`3::.ctor(object,
+ native int)
+ IL_000c: ret
+ }
+
+}
+
+.class private abstract auto ansi sealed ''.$assembly
+ extends [runtime]System.Object
+{
+ .method public static void main@() cil managed
+ {
+ .entrypoint
+
+ .maxstack 8
+ IL_0000: ret
+ }
+
+}
+
+
+
+
+
+
diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateGenericInstanceMethod.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateGenericInstanceMethod.fs
new file mode 100644
index 00000000000..535bee3d582
--- /dev/null
+++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateGenericInstanceMethod.fs
@@ -0,0 +1,13 @@
+module DelegateGenericInstanceMethod
+
+open System
+
+type C() =
+ member _.IMc<'T> (x: 'T) (y: 'T) : unit = ()
+ member _.IMt<'T> (x: 'T, y: 'T) : unit = ()
+
+// 5. eta generic instance method (curried application)
+let case5_etaCurried (o: C) = Action(fun a b -> o.IMc a b)
+
+// 35. eta generic instance method, tupled application
+let case35_etaTupled (o: C) = Action(fun a b -> o.IMt(a, b))
diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateGenericInstanceMethod.fs.OptimizeOff.Preview.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateGenericInstanceMethod.fs.OptimizeOff.Preview.il.bsl
new file mode 100644
index 00000000000..12696733399
--- /dev/null
+++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateGenericInstanceMethod.fs.OptimizeOff.Preview.il.bsl
@@ -0,0 +1,179 @@
+
+
+
+
+
+.assembly extern runtime { }
+.assembly extern FSharp.Core { }
+.assembly assembly
+{
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.FSharpInterfaceDataVersionAttribute::.ctor(int32,
+ int32,
+ int32) = ( 01 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 )
+
+
+
+
+ .hash algorithm 0x00008004
+ .ver 0:0:0:0
+}
+.module assembly.exe
+
+.imagebase {value}
+.file alignment 0x00000200
+.stackreserve 0x00100000
+.subsystem 0x0003
+.corflags 0x00000001
+
+
+
+
+
+.class public abstract auto ansi sealed assembly
+ extends [runtime]System.Object
+{
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 07 00 00 00 00 00 )
+ .class auto ansi serializable nested public C
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 )
+ .method public specialname rtspecialname instance void .ctor() cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: callvirt instance void [runtime]System.Object::.ctor()
+ IL_0006: ldarg.0
+ IL_0007: pop
+ IL_0008: ret
+ }
+
+ .method public hidebysig instance void IMc(!!T x, !!T y) cil managed
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationArgumentCountsAttribute::.ctor(int32[]) = ( 01 00 02 00 00 00 01 00 00 00 01 00 00 00 00 00 )
+
+ .maxstack 8
+ IL_0000: ret
+ }
+
+ .method public hidebysig instance void IMt(!!T x, !!T y) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ret
+ }
+
+ }
+
+ .class auto autochar serializable sealed nested assembly beforefieldinit specialname case5_etaCurried@10
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 06 00 00 00 00 00 )
+ .field public class assembly/C o
+ .method public specialname rtspecialname instance void .ctor(class assembly/C o) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldarg.1
+ IL_0002: stfld class assembly/C assembly/case5_etaCurried@10::o
+ IL_0007: ldarg.0
+ IL_0008: call instance void [runtime]System.Object::.ctor()
+ IL_000d: ret
+ }
+
+ .method assembly hidebysig instance void Invoke(int32 a, int32 b) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldfld class assembly/C assembly/case5_etaCurried@10::o
+ IL_0006: ldarg.1
+ IL_0007: ldarg.2
+ IL_0008: callvirt instance void assembly/C::IMc(!!0,
+ !!0)
+ IL_000d: nop
+ IL_000e: ret
+ }
+
+ }
+
+ .class auto autochar serializable sealed nested assembly beforefieldinit specialname case35_etaTupled@13
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 06 00 00 00 00 00 )
+ .field public class assembly/C o
+ .method public specialname rtspecialname instance void .ctor(class assembly/C o) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldarg.1
+ IL_0002: stfld class assembly/C assembly/case35_etaTupled@13::o
+ IL_0007: ldarg.0
+ IL_0008: call instance void [runtime]System.Object::.ctor()
+ IL_000d: ret
+ }
+
+ .method assembly hidebysig instance void Invoke(int32 a, int32 b) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldfld class assembly/C assembly/case35_etaTupled@13::o
+ IL_0006: ldarg.1
+ IL_0007: ldarg.2
+ IL_0008: callvirt instance void assembly/C::IMt(!!0,
+ !!0)
+ IL_000d: nop
+ IL_000e: ret
+ }
+
+ }
+
+ .method public static class [runtime]System.Action`2 case5_etaCurried(class assembly/C o) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: newobj instance void assembly/case5_etaCurried@10::.ctor(class assembly/C)
+ IL_0006: ldftn instance void assembly/case5_etaCurried@10::Invoke(int32,
+ int32)
+ IL_000c: newobj instance void class [runtime]System.Action`2::.ctor(object,
+ native int)
+ IL_0011: ret
+ }
+
+ .method public static class [runtime]System.Action`2 case35_etaTupled(class assembly/C o) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: newobj instance void assembly/case35_etaTupled@13::.ctor(class assembly/C)
+ IL_0006: ldftn instance void assembly/case35_etaTupled@13::Invoke(int32,
+ int32)
+ IL_000c: newobj instance void class [runtime]System.Action`2::.ctor(object,
+ native int)
+ IL_0011: ret
+ }
+
+}
+
+.class private abstract auto ansi sealed ''.$assembly
+ extends [runtime]System.Object
+{
+ .method public static void main@() cil managed
+ {
+ .entrypoint
+
+ .maxstack 8
+ IL_0000: ret
+ }
+
+}
+
+
+
+
+
+
diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateGenericInstanceMethod.fs.OptimizeOff.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateGenericInstanceMethod.fs.OptimizeOff.il.bsl
new file mode 100644
index 00000000000..12696733399
--- /dev/null
+++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/DirectDelegates/DelegateGenericInstanceMethod.fs.OptimizeOff.il.bsl
@@ -0,0 +1,179 @@
+
+
+
+
+
+.assembly extern runtime { }
+.assembly extern FSharp.Core { }
+.assembly assembly
+{
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.FSharpInterfaceDataVersionAttribute::.ctor(int32,
+ int32,
+ int32) = ( 01 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 )
+
+
+
+
+ .hash algorithm 0x00008004
+ .ver 0:0:0:0
+}
+.module assembly.exe
+
+.imagebase {value}
+.file alignment 0x00000200
+.stackreserve 0x00100000
+.subsystem 0x0003
+.corflags 0x00000001
+
+
+
+
+
+.class public abstract auto ansi sealed assembly
+ extends [runtime]System.Object
+{
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 07 00 00 00 00 00 )
+ .class auto ansi serializable nested public C
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 )
+ .method public specialname rtspecialname instance void .ctor() cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: callvirt instance void [runtime]System.Object::.ctor()
+ IL_0006: ldarg.0
+ IL_0007: pop
+ IL_0008: ret
+ }
+
+ .method public hidebysig instance void IMc(!!T x, !!T y) cil managed
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationArgumentCountsAttribute::.ctor(int32[]) = ( 01 00 02 00 00 00 01 00 00 00 01 00 00 00 00 00 )
+
+ .maxstack 8
+ IL_0000: ret
+ }
+
+ .method public hidebysig instance void IMt(!!T x, !!T y) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ret
+ }
+
+ }
+
+ .class auto autochar serializable sealed nested assembly beforefieldinit specialname case5_etaCurried@10
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 06 00 00 00 00 00 )
+ .field public class assembly/C o
+ .method public specialname rtspecialname instance void .ctor(class assembly/C o) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldarg.1
+ IL_0002: stfld class assembly/C assembly/case5_etaCurried@10::o
+ IL_0007: ldarg.0
+ IL_0008: call instance void [runtime]System.Object::.ctor()
+ IL_000d: ret
+ }
+
+ .method assembly hidebysig instance void Invoke(int32 a, int32 b) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldfld class assembly/C assembly/case5_etaCurried@10::o
+ IL_0006: ldarg.1
+ IL_0007: ldarg.2
+ IL_0008: callvirt instance void assembly/C::IMc(!!0,
+ !!0)
+ IL_000d: nop
+ IL_000e: ret
+ }
+
+ }
+
+ .class auto autochar serializable sealed nested assembly beforefieldinit specialname case35_etaTupled@13
+ extends [runtime]System.Object
+ {
+ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 06 00 00 00 00 00 )
+ .field public class assembly/C o
+ .method public specialname rtspecialname instance void .ctor(class assembly/C o) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldarg.1
+ IL_0002: stfld class assembly/C assembly/case35_etaTupled@13::o
+ IL_0007: ldarg.0
+ IL_0008: call instance void [runtime]System.Object::.ctor()
+ IL_000d: ret
+ }
+
+ .method assembly hidebysig instance void Invoke(int32 a, int32 b) cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldfld class assembly/C assembly/case35_etaTupled@13::o
+ IL_0006: ldarg.1
+ IL_0007: ldarg.2
+ IL_0008: callvirt instance void assembly/C::IMt