Skip to content

Recognize IsApplePlatform through OS guard attributes - #56576

Draft
makazeu wants to merge 4 commits into
dotnet:mainfrom
makazeu:u/makazeu/isappleplatform-analyzer-support
Draft

makazeu wants to merge 4 commits into
dotnet:mainfrom
makazeu:u/makazeu/isappleplatform-analyzer-support

Conversation

@makazeu

@makazeu makazeu commented Oct 10, 2026 •

Copy link
Copy Markdown

Summary

Teach the platform compatibility analyzer (CA1416) to recognize OperatingSystem.IsApplePlatform() through the SupportedOSPlatformGuard attributes on the API, rather than duplicating the Apple platform list in the analyzer.

This supports the annotated public API in dotnet/runtime#135475 and addresses #56576 (comment). The API currently declares macOS, iOS, tvOS, and MacCatalyst. The analyzer reads those declarations from the method symbol in the analyzed compilation; no runtime reflection or execution is involved.

This PR changes only the analyzer and its tests. It adds no public API.

Problem

The existing convention treats static, parameterless Boolean OperatingSystem.Is* methods as checks for an individual platform. For IsApplePlatform(), stripping Is produces a fictitious ApplePlatform instead of the platform union declared by its attributes.

Simply adding attributes does not avoid this inference: the known-guard invocation path first decodes the method name and then merges guard attributes. In addition, CheckDependentPlatforms interprets the attributes as relationships involving the inferred platform. Applying that logic to the aggregate guard can overwrite existing iOS/MacCatalyst relationships.

The original version of this PR hardcoded four platform values. This revision removes that duplication and reuses the existing attribute-driven path.

Implementation

  • Exclude IsApplePlatform from NameAndParametersValid, so it is neither decoded as a single-platform name nor passed to CheckDependentPlatforms.
  • Let OperationVisitor handle the method through its existing TryParseGuardAttributes fallback. That reads the API's attributes, parses their platform names and versions, and merges supported guards with OR semantics.
  • Remove the hardcoded IsApplePlatform case from PlatformMethodValue.TryDecode.
  • Leave name-based handling for existing guards such as IsIOS() unchanged, including their supplemental platform attributes and relationships.

Returning false from NameAndParametersValid keeps IsApplePlatform out of _platformCheckMethods. When VisitInvocation_NonLambdaOrDelegateOrLocalFunction visits the call, the membership check fails and execution takes the else branch that calls TryParseGuardAttributes. The method is still analyzed, not ignored; only name-based decoding and dependency inference are bypassed.

With the current API annotations, the positive branch is reachable on any of the four declared Apple platforms, and the negative branch excludes all four. An API called in the positive branch must be safe throughout that platform set. Windows and watchOS remain outside the set.

Only the method identity is special-cased; the platform set is defined by the referenced API's annotations, not by an analyzer-maintained list.

Regression test setup

The existing test helpers use reference assemblies without OperatingSystem.IsApplePlatform(), so the C# and Visual Basic tests provide a source stub with the API's actual guard attributes. Stub methods throw NotImplementedException; the test code is compiled and analyzed, not executed.

The tests configure the supported-platform list as macos,maccatalyst,ios,tvos,watchos,windows. Windows and watchOS serve as negative controls. Diagnostic markers assert both required warnings and required suppression.

Test coverage

Five test methods produce 13 Apple-guard cases:

  • C# positive guard and else: APIs supported across the whole Apple set, partial support, unsupported Apple platforms, and Windows/watchOS controls.
  • Explicit negation: four data rows independently verify exclusion of each currently declared Apple platform.
  • Visual Basic: positive and negative guards with the annotated API stub.
  • Metadata-driven decoding: six data rows change the stub's declared platform, including Windows and watchOS as synthetic controls, to prove the result follows attributes rather than a hardcoded Apple set. Both positive and negative branches are checked.
  • iOS/MacCatalyst relationships: verify that the aggregate guard's presence does not alter IsIOS(), IsMacCatalyst(), or their combined conditions.

Validation

Analyzer solution build:

$env:DOTNET_ADD_GLOBAL_TOOLS_TO_PATH = '0'
.\build.cmd -projects src\Microsoft.CodeAnalysis.NetAnalyzers\Microsoft.CodeAnalysis.NetAnalyzers.slnx -c Debug

Result: passed, 0 warnings, 0 errors.

Platform compatibility test class, rerun on the final patch:

.\.dotnet\dotnet.exe scripts\RunTests.cs -- `
    --project src\Microsoft.CodeAnalysis.NetAnalyzers\tests\Microsoft.CodeAnalysis.NetAnalyzers.UnitTests\Microsoft.CodeAnalysis.NetAnalyzers.UnitTests.csproj `
    --filter "FullyQualifiedName~Microsoft.NetCore.Analyzers.InteropServices.UnitTests.PlatformCompatabilityAnalyzerTests" `
    --skip-redist-check

Result: 351 total, 349 passed, 2 existing skipped tests, 0 failed on Windows ARM64.

Before the production fix, the same runner with --filter "FullyQualifiedName~IsApplePlatform_" executed 13 cases against the original hardcoded implementation: 7 failed and 6 passed. The failures were the six metadata-driven cases and the iOS/MacCatalyst relationship regression. All 13 pass after the fix.

git diff --check also passed. The full SDK test suite was not run.

Note

This PR description and the latest implementation update were prepared with GitHub Copilot.

Decode OperatingSystem.IsApplePlatform as macOS, MacCatalyst, iOS, or tvOS instead of a fictitious ApplePlatform OS. Preserve existing builtin guard registration and flow-analysis negation.

Add C# and VB regressions for allow lists, deny lists, each Apple platform, Windows, watchOS, and negation. Document builtin guard test setup.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: ee6e48ad-6898-45d7-98ed-9850afb3bd30
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 2 pipeline(s).
1 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

Restore AGENTS.md to the original baseline as requested, preserving IsApplePlatform analyzer changes and regression tests.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: ee6e48ad-6898-45d7-98ed-9850afb3bd30
{
if (invokedPlatformCheckMethod.Name == IsApplePlatform)
{
infosBuilder.Add(new PlatformMethodValue("macos", EmptyVersion, negated: false));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the latest changes in dotnet/runtime#135475, should this rather depend on the list of Apple platforms on the IsApplePlatform API instead of duplicating it here?

@makazeu makazeu Nov 5, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the hardcoded platform list and no longer use the name-based guard decoder for IsApplePlatform(). It now bypasses that path and uses the existing TryParseGuardAttributes fallback to read the platforms directly from the API's annotations. Added regression tests to verify this and preserve the iOS/MacCatalyst relationship.

@jkotas

jkotas commented Nov 4, 2026

Copy link
Copy Markdown
Member

The existing test helpers use .NET 6 reference assemblies,

Should the tests for this API use .NET 12 reference assemblies (once the runtime change is merged) so that they test the actual user experience?

Route IsApplePlatform through the existing guard attribute decoder instead of inferring a platform from its name or hardcoding the Apple platform set. Preserve iOS/MacCatalyst relationships and cover metadata-driven guards in regression tests.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@makazeu makazeu changed the title Recognize IsApplePlatform as a four-platform OS guard Recognize IsApplePlatform through OS guard attributes Nov 4, 2026
@makazeu

makazeu commented Nov 5, 2026

Copy link
Copy Markdown
Author

The existing test helpers use .NET 6 reference assemblies,

Should the tests for this API use .NET 12 reference assemblies (once the runtime change is merged) so that they test the actual user experience?

Once dotnet/runtime#135475 is merged and a reference pack containing the API is available, I'll switch the C# and VB API-usage tests to .NET 12 reference assemblies and remove their OperatingSystem stubs, so they exercise the real API and its guard annotations. Only the tests exercising the real API will move to .NET 12 reference assemblies; unrelated tests will continue using their existing references.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants