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 ff1c9aae2dd..d9cb8c44533 100644
--- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
+++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
@@ -68,6 +68,7 @@
* Reference assembly MVIDs are now deterministic across compiler invocations. Previously, `--refout` / `true` produced a different MVID every build because the implied signature hash used .NET's randomized `String.GetHashCode()`. ([Issue #19751](https://github.com/dotnet/fsharp/issues/19751), [PR #19801](https://github.com/dotnet/fsharp/pull/19801))
* Parser: recover on unfinished if and binary expressions
([PR #19724](https://github.com/dotnet/fsharp/pull/19724))
+* Enforce that compiler-semantic attributes (`NoDynamicInvocation`) present on a value in a `.fs` implementation file are also declared in the corresponding `.fsi` signature file. Previously such attributes were silently dropped because tooling skips implementations when a signature is present. ([Issue #19560](https://github.com/dotnet/fsharp/issues/19560), [PR #19880](https://github.com/dotnet/fsharp/pull/19880))
### Added
diff --git a/docs/release-notes/.FSharp.Core/11.0.100.md b/docs/release-notes/.FSharp.Core/11.0.100.md
index 7d0385e3b89..22f013ae729 100644
--- a/docs/release-notes/.FSharp.Core/11.0.100.md
+++ b/docs/release-notes/.FSharp.Core/11.0.100.md
@@ -3,3 +3,4 @@
* Fix `Array.exists2` documentation examples to use equal-length arrays; the previous examples would throw `ArgumentException` at runtime instead of returning the documented `false`/`true` values. ([PR #19672](https://github.com/dotnet/fsharp/pull/19672))
* Move `Async.StartChild` to the "Starting Async Computations" docs category alongside `Async.StartChildAsTask`. ([Issue #19667](https://github.com/dotnet/fsharp/issues/19667))
* Add `InlineIfLambda` to `Array.init` ([PR #19869](https://github.com/dotnet/fsharp/pull/19869))
+* Mirror `[]` from FSharp.Core implementation files into the matching `.fsi` signature files (in `nativeptr.fsi` and `prim-types.fsi`) so the attribute reaches consumers and tooling. Required by the new signature-enforced-attributes check. ([Issue #19560](https://github.com/dotnet/fsharp/issues/19560), [PR #19880](https://github.com/dotnet/fsharp/pull/19880))
diff --git a/src/Compiler/Checking/SignatureConformance.fs b/src/Compiler/Checking/SignatureConformance.fs
index 0f3d5ec13fe..c7e297bc8f2 100644
--- a/src/Compiler/Checking/SignatureConformance.fs
+++ b/src/Compiler/Checking/SignatureConformance.fs
@@ -16,6 +16,7 @@ open FSharp.Compiler.InfoReader
open FSharp.Compiler.Syntax
open FSharp.Compiler.SyntaxTreeOps
open FSharp.Compiler.Text
+open FSharp.Compiler.TcGlobals
open FSharp.Compiler.TypedTree
open FSharp.Compiler.TypedTreeBasics
open FSharp.Compiler.TypedTreeOps
@@ -41,6 +42,20 @@ exception InterfaceNotRevealed of DisplayEnv * TType * range
exception ArgumentsInSigAndImplMismatch of sigArg: Ident * implArg: Ident
+/// The set of well-known Val-level attributes whose presence on an implementation
+/// must be matched in the signature. These attributes change the contract observed
+/// by consumers of a value/member (inlining behaviour, dynamic dispatch, codegen),
+/// and tooling does not consult the implementation when a signature file is present
+/// (so attributes only on the impl are silently lost). Adding a new attribute here
+/// is the ONE PLACE required to enforce its presence in the signature.
+let private signatureEnforcedAttributes (g: TcGlobals) : (string * (Val -> bool)) list =
+ [
+ "NoDynamicInvocation",
+ (fun (v: Val) ->
+ ValHasWellKnownAttribute g WellKnownValAttributes.NoDynamicInvocationAttribute_True v
+ || ValHasWellKnownAttribute g WellKnownValAttributes.NoDynamicInvocationAttribute_False v)
+ ]
+
exception DefinitionsInSigAndImplNotCompatibleAbbreviationsDiffer of
denv: DisplayEnv *
implTycon:Tycon *
@@ -368,6 +383,13 @@ type Checker(g, amap, denv, remapInfo: SignatureRepackageInfo, checkingSig) =
let mk_err kind denv f = ValueNotContained(kind,denv, infoReader, implModRef, implVal, sigVal, f)
let err denv f = errorR(mk_err RegularMismatch denv f); false
let m = implVal.Range
+
+ // Enforce that compiler-semantic attributes present on the implementation
+ // are also present on the signature. See `signatureEnforcedAttributes` for
+ // the list and rationale.
+ for (attrName, hasAttr) in signatureEnforcedAttributes g do
+ if hasAttr implVal && not (hasAttr sigVal) then
+ errorR(Error (FSComp.SR.implAttributeMissingFromSignature(attrName, implVal.DisplayName), m))
if implVal.IsMutable <> sigVal.IsMutable then (err denv FSComp.SR.ValueNotContainedMutabilityAttributesDiffer)
elif implVal.LogicalName <> sigVal.LogicalName then (err denv FSComp.SR.ValueNotContainedMutabilityNamesDiffer)
elif (implVal.CompiledName g.CompilerGlobalState) <> (sigVal.CompiledName g.CompilerGlobalState) then (err denv FSComp.SR.ValueNotContainedMutabilityCompiledNamesDiffer)
diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt
index f9765bdbd6e..86e4422c96f 100644
--- a/src/Compiler/FSComp.txt
+++ b/src/Compiler/FSComp.txt
@@ -1818,4 +1818,5 @@ featurePreprocessorElif,"#elif preprocessor directive"
3885,parsLetBangCannotBeLastInCE,"'%s' cannot be the final expression in a computation expression. Finish with 'return', 'return!', or a simple expression."
3886,tcListLiteralWithSingleTupleElement,"This list expression contains a single tuple element. Did you mean to use ';' instead of ',' to separate list elements?"
3887,ilCustomAttrInvalidArrayElemType,"The type '%s' is not a valid custom attribute argument type. Custom attribute arrays must have elements of primitive types, enums, string, System.Type, or System.Object."
+3888,implAttributeMissingFromSignature,"The attribute '%s' is present on '%s' in the implementation but not in the signature. Add the attribute to the signature, because tooling skips the implementation when a signature file is present."
featureExceptionFieldSerializationSupport,"emit GetObjectData and field-restoring deserialization constructor for exception types"
diff --git a/src/Compiler/xlf/FSComp.txt.cs.xlf b/src/Compiler/xlf/FSComp.txt.cs.xlf
index 10fe84e6ab1..0247d7d30c3 100644
--- a/src/Compiler/xlf/FSComp.txt.cs.xlf
+++ b/src/Compiler/xlf/FSComp.txt.cs.xlf
@@ -802,6 +802,11 @@
Konstruktor obnovitelného kódu {0} se dá použít jenom ve vloženém kódu chráněném příkazem if __useResumableCode then ... a celkové složení musí tvořit platný obnovitelný kód.
+
+ The attribute '{0}' is present on '{1}' in the implementation but not in the signature. Add the attribute to the signature, because tooling skips the implementation when a signature file is present.
+ The attribute '{0}' is present on '{1}' in the implementation but not in the signature. Add the attribute to the signature, because tooling skips the implementation when a signature file is present.
+
+ The 'InlineIfLambda' attribute is present in the signature but not the implementation.Atribut InlineIfLambda se nachází v signatuře, ale ne v implementaci.
@@ -8972,21 +8977,21 @@
This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments.
-
- '{0}' cannot be the final expression in a computation expression. Finish with 'return', 'return!', or a simple expression.
- '{0}' cannot be the final expression in a computation expression. Finish with 'return', 'return!', or a simple expression.
-
-
-
- This list expression contains a single tuple element. Did you mean to use ';' instead of ',' to separate list elements?
- This list expression contains a single tuple element. Did you mean to use ';' instead of ',' to separate list elements?
-
-
-
- emit GetObjectData and field-restoring deserialization constructor for exception types
- emit GetObjectData and field-restoring deserialization constructor for exception types
-
-
+
+ '{0}' cannot be the final expression in a computation expression. Finish with 'return', 'return!', or a simple expression.
+ '{0}' cannot be the final expression in a computation expression. Finish with 'return', 'return!', or a simple expression.
+
+
+
+ This list expression contains a single tuple element. Did you mean to use ';' instead of ',' to separate list elements?
+ This list expression contains a single tuple element. Did you mean to use ';' instead of ',' to separate list elements?
+
+
+
+ emit GetObjectData and field-restoring deserialization constructor for exception types
+ emit GetObjectData and field-restoring deserialization constructor for exception types
+
+