diff --git a/.github/instructions/NoBloat.instructions.md b/.github/instructions/NoBloat.instructions.md new file mode 100644 index 00000000000..33732deee7c --- /dev/null +++ b/.github/instructions/NoBloat.instructions.md @@ -0,0 +1,55 @@ +--- +applyTo: + - "src/Compiler/**/*.{fs,fsi}" + - "vsintegration/src/**/*.{fs,fsi}" + - "tests/FSharp.Compiler.ComponentTests/**/*.fs" + - "tests/FSharp.Compiler.Service.Tests/**/*.fs" + - "vsintegration/tests/**/*.fs" +--- + +# Code Style: No Bloat + +Reviewers read code, not prose. Add bytes only when they pay for themselves. + +## Comments + +Good names beat comments **24/7**. Before writing a comment, ask: *can I rename a value, extract a function, or use an active pattern so the comment becomes unnecessary?* If yes, do that instead. + +- **Do not** restate what the code says (variable name, type name, attribute name, function signature). +- **Do not** narrate the algorithm step-by-step. The diff is the algorithm. +- **Do not** justify design decisions inline ("we chose X over Y because…"). Put rationale in the commit message or PR body. +- **Do not** leave war-story comments ("previously we did Z, but…", "counter-example: …"). The history is in `git log`. +- **Do not** write multi-line `///` doc comments for internal helpers whose body is one expression. + +Acceptable comments answer **why**, not **what**, and only when the *why* is non-obvious and cannot be expressed by renaming: +- Workarounds for compiler/runtime bugs (link the bug). +- Performance constraints invisible from the code shape ("inner loop runs 50M times per typecheck"). +- Cross-file invariants the code itself can't enforce. + +If you are tempted to write `// This is intentional`, change the code so the intent is structural, not decorative. + +## Code shape + +- Compact, idiomatic F#: pattern matching over `if`/`elif` ladders; active patterns where they remove duplication. +- Low cyclomatic complexity per function. Extract helpers — even one-line ones — when a name clarifies a step. +- Prefer module-level `let` over big bodies with nested locals. +- New file > bloating an existing 5000-line file when adding a self-contained concept. + +## Test code + +Tests get a touch more leeway for explanation, but the same rules largely apply: + +- One parametrized test (`[]`, `[]`, or a `for/yield` over inputs) > five copy-pasted tests. +- Module-level constants for shared paths (`Path.Combine` for OS neutrality), shared source strings, and shared expected outputs. +- Helpers like `parseAndCheck code` over reinventing the setup per test. +- Don't reinvent an entire `.fs` file inside each test when one shared module-level binding will do. + +## PR scope + +**Not paid by LOC.** Large PRs are typically shitty PRs. If the diff has 1000+ lines, split it. +- Cleanup commits separate from feature commits. +- No "phase tag" / "transitional measure" / "follow-up" comments left behind — either do it now in a follow-up commit, or file an issue. Don't leave breadcrumbs in the code. + +## When in doubt + +Delete the comment, rename the value, and re-read. If the code is still unclear, *that* is what needs fixing — not the comment. 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 965957dbc0c..0db632dc13a 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -73,6 +73,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)) +* 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)) diff --git a/docs/release-notes/.Language/preview.md b/docs/release-notes/.Language/preview.md index d97ef294125..90c1aa2faa6 100644 --- a/docs/release-notes/.Language/preview.md +++ b/docs/release-notes/.Language/preview.md @@ -2,6 +2,7 @@ * 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)) ### Fixed diff --git a/docs/release-notes/.VisualStudio/18.vNext.md b/docs/release-notes/.VisualStudio/18.vNext.md index 781af946d89..abf918a6d38 100644 --- a/docs/release-notes/.VisualStudio/18.vNext.md +++ b/docs/release-notes/.VisualStudio/18.vNext.md @@ -1,3 +1,7 @@ +### Added + +* Code-fixes for FS3888 (compiler-semantic attribute on the `.fs` but not the `.fsi`): copy the attribute into the `.fsi`, or remove it from the `.fs`. ([Issue #19560](https://github.com/dotnet/fsharp/issues/19560), [PR #19880](https://github.com/dotnet/fsharp/pull/19880)) + ### Fixed * Fixed Rename incorrectly renaming `get` and `set` keywords for properties with explicit accessors. ([Issue #18270](https://github.com/dotnet/fsharp/issues/18270), [PR #19252](https://github.com/dotnet/fsharp/pull/19252)) diff --git a/src/Compiler/AbstractIL/il.fsi b/src/Compiler/AbstractIL/il.fsi index 3539e235518..cbc6272ef05 100644 --- a/src/Compiler/AbstractIL/il.fsi +++ b/src/Compiler/AbstractIL/il.fsi @@ -854,6 +854,7 @@ type ILAttribElem = type ILAttributeNamedArg = string * ILType * bool * ILAttribElem /// Custom attribute. +[] type ILAttribute = /// Attribute with args encoded to a binary blob according to ECMA-335 II.21 and II.23.3. /// 'decodeILAttribData' is used to parse the byte[] blob to ILAttribElem's as best as possible. @@ -973,6 +974,7 @@ type internal ILSecurityAction = | InheritanceDemandChoice | DemandChoice +[] type internal ILSecurityDecl = ILSecurityDecl of ILSecurityAction * byte[] /// Abstract type equivalent to ILSecurityDecl list - use helpers @@ -1041,6 +1043,7 @@ type MethodBody = | NotAvailable /// Generic parameters. Formal generic parameter declarations may include the bounds, if any, on the generic parameter. +[] type ILGenericParameterDef = { Name: string diff --git a/src/Compiler/AbstractIL/ilprint.fs b/src/Compiler/AbstractIL/ilprint.fs index 12e421f6829..f1fb38174c9 100644 --- a/src/Compiler/AbstractIL/ilprint.fs +++ b/src/Compiler/AbstractIL/ilprint.fs @@ -333,7 +333,7 @@ and goutput_permission _env os p = | ILSecurityAction.DemandChoice -> "demandchoice") match p with - | ILSecurityDecl(sa, b) -> + | ILSecurityDecl.ILSecurityDecl(sa, b) -> output_string os " .permissionset " output_security_action os sa output_string os " = (" diff --git a/src/Compiler/AbstractIL/ilread.fs b/src/Compiler/AbstractIL/ilread.fs index 7754fedad20..09fc311367a 100644 --- a/src/Compiler/AbstractIL/ilread.fs +++ b/src/Compiler/AbstractIL/ilread.fs @@ -3358,7 +3358,7 @@ and securityDeclsReader ctxtH tag = |> List.toArray) and seekReadSecurityDecl ctxt (act, ty) = - ILSecurityDecl( + ILSecurityDecl.ILSecurityDecl( (if List.memAssoc (int act) (Lazy.force ILSecurityActionRevMap) then List.assoc (int act) (Lazy.force ILSecurityActionRevMap) else diff --git a/src/Compiler/AbstractIL/ilwrite.fs b/src/Compiler/AbstractIL/ilwrite.fs index 7751a8d696b..13feeab294a 100644 --- a/src/Compiler/AbstractIL/ilwrite.fs +++ b/src/Compiler/AbstractIL/ilwrite.fs @@ -1506,7 +1506,7 @@ and GenCustomAttrsPass3Or4 cenv hca (attrs: ILAttributes) = // ILSecurityDecl --> DeclSecurity rows // -------------------------------------------------------------------- *) -let rec GetSecurityDeclRow cenv hds (ILSecurityDecl (action, s)) = +let rec GetSecurityDeclRow cenv hds (ILSecurityDecl.ILSecurityDecl (action, s)) = UnsharedRow [| UShort (uint16 (List.assoc action (Lazy.force ILSecurityActionMap))) HasDeclSecurity (fst hds, snd hds) diff --git a/src/Compiler/Checking/NameResolution.fs b/src/Compiler/Checking/NameResolution.fs index 36eedebfbe8..314dbe8cc9a 100644 --- a/src/Compiler/Checking/NameResolution.fs +++ b/src/Compiler/Checking/NameResolution.fs @@ -1990,7 +1990,7 @@ let ItemsAreEffectivelyEqual g orig other = | TType_var (tp1, _), TType_var (tp2, _) -> not tp1.IsCompilerGenerated && not tp1.IsFromError && not tp2.IsCompilerGenerated && not tp2.IsFromError && - equals tp1.Range tp2.Range + Range.equals tp1.Range tp2.Range | AbbrevOrAppTy(tcref1, _), AbbrevOrAppTy(tcref2, _) -> tyconRefDefnEq g tcref1 tcref2 | _ -> false) diff --git a/src/Compiler/Checking/SignatureConformance.fs b/src/Compiler/Checking/SignatureConformance.fs index 0f3d5ec13fe..6bba1104945 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,131 @@ exception InterfaceNotRevealed of DisplayEnv * TType * range exception ArgumentsInSigAndImplMismatch of sigArg: Ident * implArg: Ident +type private V = WellKnownValAttributes +type private E = WellKnownEntityAttributes + +module private AttributeConformance = + + // Each row: a well-known attribute that is read off the .fsi by the + // typechecker / IDE. Runtime-only attributes (DllImport, ReflectedDefinition, + // SkipLocalsInit, ...) deliberately not listed. + let private enforcedVals : V list = [ + V.NoDynamicInvocationAttribute_True ||| V.NoDynamicInvocationAttribute_False + V.RequiresExplicitTypeArgumentsAttribute + V.ConditionalAttribute + V.NoEagerConstraintApplicationAttribute + V.GeneralizableValueAttribute + V.WarnOnWithoutNullArgumentAttribute + V.CLIEventAttribute + V.ExtensionAttribute + V.ParamArrayAttribute + V.LiteralAttribute + ] + + let private enforcedEntities : E list = [ + E.RequireQualifiedAccessAttribute + // AutoOpen on .fs alone is a no-op for F# consumers (.fsi wins). + E.NoComparisonAttribute + E.NoEqualityAttribute + // StructuralEquality / StructuralComparison are documentary for consumers + // (F# default already generates them); load-bearing only as impl-side FS1176/FS1177. + E.CustomEqualityAttribute + E.CustomComparisonAttribute + E.ReferenceEqualityAttribute + E.AbstractClassAttribute + E.SealedAttribute_True ||| E.SealedAttribute_False + E.CLIMutableAttribute + E.AllowNullLiteralAttribute_True ||| E.AllowNullLiteralAttribute_False + E.DefaultAugmentationAttribute_True ||| E.DefaultAugmentationAttribute_False + E.ObsoleteAttribute + E.CompilerMessageAttribute + E.ExperimentalAttribute + E.UnverifiableAttribute + E.EditorBrowsableAttribute + E.AttributeUsageAttribute + E.IsByRefLikeAttribute + E.IsReadOnlyAttribute + E.ExtensionAttribute + E.MeasureAttribute + E.StructAttribute + E.ClassAttribute + E.InterfaceAttribute + ] + + let private enforcedValsMask : V = List.reduce Flags.union enforcedVals + let private enforcedEntitiesMask : E = List.reduce Flags.union enforcedEntities + + // `AutoOpenAttribute` -> `"AutoOpen"`, `SealedAttribute_True ||| _False` -> `"Sealed"`. + let inline private displayName< ^T when ^T : enum > (flag: ^T) : string = + let v = LanguagePrimitives.EnumToValue flag + let lsb : ^T = LanguagePrimitives.EnumOfValue (v &&& (0uL - v)) + let s = string lsb + let s = if s.EndsWith "_True" then s.Substring(0, s.Length - 5) + elif s.EndsWith "_False" then s.Substring(0, s.Length - 6) + else s + if s.EndsWith "Attribute" then s.Substring(0, s.Length - 9) else s + + // Point the squiggle at the offending impl attribute, not the value/type identifier. + let inline private rangeOfMissing + (classify: Attrib -> 'F) + (attribs: Attrib list) + (bits: 'F) + (fallback: range) + : range = + match attribs |> List.tryFind (fun a -> classify a |> Flags.intersects bits) with + | Some a -> a.Range + | None -> fallback + + let inline private checkEnforced + (emit: exn -> unit) + (enforcedFlagsOn: 'Subject -> 'F) + (policy: 'F list) + (attribsOf: 'Subject -> Attrib list) + (classify: Attrib -> 'F) + (displayNameOf: 'Subject -> string) + (impl: 'Subject) (sig': 'Subject) (fallback: range) = + let presentOnImplAndRequiredFromSig = enforcedFlagsOn impl + if not (Flags.isEmpty presentOnImplAndRequiredFromSig) then + let actuallyPresentInSig = enforcedFlagsOn sig' + if not (presentOnImplAndRequiredFromSig |> Flags.isSubsetOf actuallyPresentInSig) then + let missing = presentOnImplAndRequiredFromSig |> Flags.except actuallyPresentInSig + let implAttribs = attribsOf impl + for flag in policy do + if flag |> Flags.intersects missing then + let m = rangeOfMissing classify implAttribs flag fallback + emit(Error (FSComp.SR.implAttributeMissingFromSignature(displayName flag, displayNameOf impl), m)) + + let private emitter (g: TcGlobals) : exn -> unit = + if g.langVersion.SupportsFeature LanguageFeature.ErrorOnMissingSignatureAttribute then + errorR + else + warning + + let checkVal (g: TcGlobals) (implVal: Val) (sigVal: Val) (fallback: range) = + let enforcedFlagsOnVal (v: Val) = + ValHasWellKnownAttribute g enforcedValsMask v |> ignore // forceload + v.ValAttribs.Flags |> Flags.intersect enforcedValsMask + checkEnforced (emitter g) enforcedFlagsOnVal enforcedVals + (fun (v: Val) -> v.Attribs) + (classifyValAttrib g) + (fun (v: Val) -> v.DisplayName) + implVal sigVal fallback + + let checkEntity (g: TcGlobals) (implEntity: Entity) (sigEntity: Entity) (fallback: range) = + // Skip when the sig declares a non-module entity with hidden representation + // (e.g. `type internal T` opaque in .fsi vs DU in .fs): structural-equality + // / NoEquality attributes can't be applied to opaque type declarations. + // Modules report IsHiddenReprTycon=true but accept RequireQualifiedAccess. + if sigEntity.IsModuleOrNamespace || not sigEntity.IsHiddenReprTycon then + let enforcedFlagsOnEntity (e: Entity) = + EntityHasWellKnownAttribute g enforcedEntitiesMask e |> ignore // forceload + e.EntityAttribs.Flags |> Flags.intersect enforcedEntitiesMask + checkEnforced (emitter g) enforcedFlagsOnEntity enforcedEntities + (fun (e: Entity) -> e.Attribs) + (classifyEntityAttrib g) + (fun (e: Entity) -> e.DisplayName) + implEntity sigEntity fallback + exception DefinitionsInSigAndImplNotCompatibleAbbreviationsDiffer of denv: DisplayEnv * implTycon:Tycon * @@ -126,6 +252,14 @@ type Checker(g, amap, denv, remapInfo: SignatureRepackageInfo, checkingSig) = fixup (sigAttribs @ keptImplAttribs) true + // Pull the enforcement-relevant subset of an Entity's / Val's flags, + // forcing the cached `Flags` to be populated as a side-effect. + let checkEnforcedEntityAttribs implEntity sigEntity m = + AttributeConformance.checkEntity g implEntity sigEntity m + + let checkEnforcedValAttribs implVal sigVal m = + AttributeConformance.checkVal g implVal sigVal m + let rec checkTypars m (aenv: TypeEquivEnv) (implTypars: Typars) (sigTypars: Typars) = if implTypars.Length <> sigTypars.Length then errorR (Error(FSComp.SR.typrelSigImplNotCompatibleParamCountsDiffer(), m)) @@ -185,6 +319,12 @@ type Checker(g, amap, denv, remapInfo: SignatureRepackageInfo, checkingSig) = // Propagate defn location information from implementation to signature . sigTycon.SetOtherRange (implTycon.Range, true) implTycon.SetOtherRange (sigTycon.Range, false) + + // Enforce that compiler-semantic entity attributes present on the + // implementation are also present on the signature. See + // `signatureEnforcedEntityAttribs` for the list and rationale. + // Emitted as a warning (will become an error in a future F# version). + checkEnforcedEntityAttribs implTycon sigTycon m if implTycon.LogicalName <> sigTycon.LogicalName then errorR (Error (FSComp.SR.DefinitionsInSigAndImplNotCompatibleNamesDiffer(implTycon.TypeOrMeasureKind.ToString(), sigTycon.LogicalName, implTycon.LogicalName), m)) @@ -368,6 +508,8 @@ 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 + + checkEnforcedValAttribs implVal sigVal 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) @@ -753,6 +895,10 @@ type Checker(g, amap, denv, remapInfo: SignatureRepackageInfo, checkingSig) = // Propagate defn location information from implementation to signature . sigModRef.SetOtherRange (implModRef.Range, true) implModRef.Deref.SetOtherRange (sigModRef.Range, false) + // Enforce consumer-visible compiler-semantic attributes on the module + // itself (e.g. [] on a nested module). Same rationale as + // checkTypeDef. Emitted as a warning (will become an error later). + checkEnforcedEntityAttribs implModRef.Deref sigModRef implModRef.Range checkModuleOrNamespaceContents implModRef.Range aenv infoReader implModRef sigModRef.ModuleOrNamespaceType && checkAttribs aenv implModRef.Attribs sigModRef.Attribs implModRef.Deref.SetAttribs diff --git a/src/Compiler/CodeGen/EraseUnions.fs b/src/Compiler/CodeGen/EraseUnions.fs index 7a8f2bb9b2a..12299958a06 100644 --- a/src/Compiler/CodeGen/EraseUnions.fs +++ b/src/Compiler/CodeGen/EraseUnions.fs @@ -694,9 +694,9 @@ let private rewriteNullableAttrForFlattenedField (g: TcGlobals) (existingAttrs: let replacementAttr = match existingAttrs[idx] with // Single byte: change non-nullable (1) to WithNull (2); leave nullable (2) and ambivalent (0) as-is - | Encoded(method, _data, [ ILAttribElem.Byte 1uy ]) -> mkILCustomAttribMethRef (method, [ ILAttribElem.Byte 2uy ], []) + | ILAttribute.Encoded(method, _data, [ ILAttribElem.Byte 1uy ]) -> mkILCustomAttribMethRef (method, [ ILAttribElem.Byte 2uy ], []) // Array of bytes: change first element only (field itself); leave generic type arg nullability unchanged - | Encoded(method, _data, [ ILAttribElem.Array(elemType, ILAttribElem.Byte 1uy :: otherElems) ]) -> + | ILAttribute.Encoded(method, _data, [ ILAttribElem.Array(elemType, ILAttribElem.Byte 1uy :: otherElems) ]) -> mkILCustomAttribMethRef (method, [ ILAttribElem.Array(elemType, (ILAttribElem.Byte 2uy) :: otherElems) ], []) | attrAsBefore -> attrAsBefore diff --git a/src/Compiler/CodeGen/IlxGen.fs b/src/Compiler/CodeGen/IlxGen.fs index 46959e56b22..1c289d6d8f1 100644 --- a/src/Compiler/CodeGen/IlxGen.fs +++ b/src/Compiler/CodeGen/IlxGen.fs @@ -3080,7 +3080,7 @@ and GenExprPreSteps (cenv: cenv) (cgbuf: CodeGenBuffer) eenv expr sequel = let others = [ for k in cenv.namedDebugPointsForInlinedCode.Keys do - if equals m k.Range then + if Range.equals m k.Range then yield k.Name ] |> String.concat "," @@ -3172,7 +3172,7 @@ and GenExprAux (cenv: cenv) (cgbuf: CodeGenBuffer) eenv expr (sequel: sequel) = | Expr.Match _ -> GenLinearExpr cenv cgbuf eenv expr sequel false id |> ignore | Expr.DebugPoint(DebugPointAtLeafExpr.Yes m, innerExpr) -> - if equals m range0 then + if Range.equals m range0 then cgbuf.EmitStartOfHiddenCode() else CG.EmitDebugPoint cgbuf m @@ -3737,7 +3737,7 @@ and GenLinearExpr cenv cgbuf eenv expr sequel preSteps (contf: FakeUnit -> FakeU Fake)) | Expr.DebugPoint(DebugPointAtLeafExpr.Yes m, innerExpr) -> - if equals m range0 then + if Range.equals m range0 then cgbuf.EmitStartOfHiddenCode() else CG.EmitDebugPoint cgbuf m diff --git a/src/Compiler/Driver/CompilerConfig.fs b/src/Compiler/Driver/CompilerConfig.fs index 0fbe48fb2eb..18b43b75ce5 100644 --- a/src/Compiler/Driver/CompilerConfig.fs +++ b/src/Compiler/Driver/CompilerConfig.fs @@ -1142,7 +1142,7 @@ type TcConfig private (data: TcConfigBuilder, validate: bool) = else // If the file doesn't exist, let reference resolution logic report the error later... defaultCoreLibraryReference, - if equals assemRef.Range rangeStartup then + if Range.equals assemRef.Range rangeStartup then Some fileName else None diff --git a/src/Compiler/Driver/CompilerDiagnostics.fs b/src/Compiler/Driver/CompilerDiagnostics.fs index 4ecbfc081ef..c340182d285 100644 --- a/src/Compiler/Driver/CompilerDiagnostics.fs +++ b/src/Compiler/Driver/CompilerDiagnostics.fs @@ -2100,7 +2100,7 @@ type FormattedDiagnostic = | Long of FSharpDiagnosticSeverity * FormattedDiagnosticDetailedInfo let FormatDiagnosticLocation (tcConfig: TcConfig) (m: Range) : FormattedDiagnosticLocation = - if equals m rangeStartup || equals m rangeCmdArgs then + if Range.equals m rangeStartup || Range.equals m rangeCmdArgs then { Range = m TextRepresentation = "" diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt index f9765bdbd6e..dc27f32bae9 100644 --- a/src/Compiler/FSComp.txt +++ b/src/Compiler/FSComp.txt @@ -1818,4 +1818,6 @@ 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, which takes precedence for tooling and consumers. Add the attribute to the signature, to ensure the attribute is not ignored by the compiler." 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" diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs index d5f87fb2e4e..2f2a1a70159 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fs +++ b/src/Compiler/Facilities/DiagnosticsLogger.fs @@ -181,7 +181,7 @@ let inline protectAssemblyExplorationNoReraise dflt1 dflt2 ([] f // Attach a range if this is a range dual exception. let rec AttachRange m (exn: exn) = - if equals m range0 then + if Range.equals m range0 then exn else match exn with diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fsi b/src/Compiler/Facilities/DiagnosticsLogger.fsi index 0f801b8b4d4..a6e787e8c8b 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fsi +++ b/src/Compiler/Facilities/DiagnosticsLogger.fsi @@ -427,6 +427,7 @@ val inline MapReduce2D: ys: 'T2 list -> OperationResult<'c> +[] module OperationResult = val inline ignore: res: OperationResult<'T> -> OperationResult diff --git a/src/Compiler/Facilities/LanguageFeatures.fs b/src/Compiler/Facilities/LanguageFeatures.fs index c66a039d7df..da4ef690311 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fs +++ b/src/Compiler/Facilities/LanguageFeatures.fs @@ -109,6 +109,7 @@ type LanguageFeature = | ImplicitDIMCoverage | PreprocessorElif | ExceptionFieldSerializationSupport + | ErrorOnMissingSignatureAttribute /// LanguageVersion management type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array) = @@ -261,6 +262,7 @@ type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array) LanguageFeature.FromEndSlicing, previewVersion // Unfinished features --- needs work LanguageFeature.MethodOverloadsCache, previewVersion // Performance optimization for overload resolution LanguageFeature.ImplicitDIMCoverage, languageVersion110 + LanguageFeature.ErrorOnMissingSignatureAttribute, previewVersion // Opt-in: turn FS3888 from warning into error ] static let defaultLanguageVersion = LanguageVersion("default") @@ -456,6 +458,7 @@ type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array) | LanguageFeature.ImplicitDIMCoverage -> FSComp.SR.featureImplicitDIMCoverage () | LanguageFeature.PreprocessorElif -> FSComp.SR.featurePreprocessorElif () | LanguageFeature.ExceptionFieldSerializationSupport -> FSComp.SR.featureExceptionFieldSerializationSupport () + | LanguageFeature.ErrorOnMissingSignatureAttribute -> FSComp.SR.featureErrorOnMissingSignatureAttribute () /// 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 4219ce43e35..5ba352191af 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fsi +++ b/src/Compiler/Facilities/LanguageFeatures.fsi @@ -100,6 +100,7 @@ type LanguageFeature = | ImplicitDIMCoverage | PreprocessorElif | ExceptionFieldSerializationSupport + | ErrorOnMissingSignatureAttribute /// LanguageVersion management type LanguageVersion = diff --git a/src/Compiler/Service/ExternalSymbol.fsi b/src/Compiler/Service/ExternalSymbol.fsi index ee63d291084..e0846faece5 100644 --- a/src/Compiler/Service/ExternalSymbol.fsi +++ b/src/Compiler/Service/ExternalSymbol.fsi @@ -26,6 +26,7 @@ module internal FindDeclExternalType = val internal tryOfILType: string array -> ILType -> FindDeclExternalType option /// Represents the type of a single method parameter +[] [] type public FindDeclExternalParam = @@ -37,6 +38,7 @@ type public FindDeclExternalParam = override ToString: unit -> string +[] module internal FindDeclExternalParam = val internal tryOfILType: string array -> ILType -> FindDeclExternalParam option diff --git a/src/Compiler/Service/ServiceCompilerDiagnostics.fsi b/src/Compiler/Service/ServiceCompilerDiagnostics.fsi index f3536054c65..10836c5ef1b 100644 --- a/src/Compiler/Service/ServiceCompilerDiagnostics.fsi +++ b/src/Compiler/Service/ServiceCompilerDiagnostics.fsi @@ -10,6 +10,7 @@ type FSharpDiagnosticKind = | RemoveIndexerDot /// Exposes compiler diagnostic error messages. +[] module CompilerDiagnostics = /// Given a DiagnosticKind, returns the string representing the error message for that diagnostic. diff --git a/src/Compiler/Service/ServiceDeclarationLists.fs b/src/Compiler/Service/ServiceDeclarationLists.fs index 98312a69306..6adb17bea12 100644 --- a/src/Compiler/Service/ServiceDeclarationLists.fs +++ b/src/Compiler/Service/ServiceDeclarationLists.fs @@ -7,6 +7,7 @@ namespace FSharp.Compiler.EditorServices + open FSharp.Compiler.NicePrint open Internal.Utilities.Library open Internal.Utilities.Library.Extras diff --git a/src/Compiler/Service/ServiceDeclarationLists.fsi b/src/Compiler/Service/ServiceDeclarationLists.fsi index 3aeaa11112d..fbf74a4ab75 100644 --- a/src/Compiler/Service/ServiceDeclarationLists.fsi +++ b/src/Compiler/Service/ServiceDeclarationLists.fsi @@ -191,7 +191,7 @@ type public MethodGroupItemParameter = /// Represents one method (or other item) in a method group. The item may represent either a method or /// a single, non-overloaded item such as union case or a named function value. -[] +[] type public MethodGroupItem = /// The documentation for the item diff --git a/src/Compiler/Service/ServiceNavigation.fs b/src/Compiler/Service/ServiceNavigation.fs index 5c15f756133..a56b4d4eb6e 100755 --- a/src/Compiler/Service/ServiceNavigation.fs +++ b/src/Compiler/Service/ServiceNavigation.fs @@ -100,8 +100,8 @@ type NavigationItems(declarations: NavigationTopLevelDeclaration[]) = module NavigationImpl = let unionRangesChecked r1 r2 = - if equals r1 range0 then r2 - elif equals r2 range0 then r1 + if Range.equals r1 range0 then r2 + elif Range.equals r2 range0 then r1 else unionRanges r1 r2 let rangeOfDecls2 f decls = diff --git a/src/Compiler/Service/ServiceNavigation.fsi b/src/Compiler/Service/ServiceNavigation.fsi index 360855cce39..cfccd6ef20c 100755 --- a/src/Compiler/Service/ServiceNavigation.fsi +++ b/src/Compiler/Service/ServiceNavigation.fsi @@ -72,6 +72,7 @@ type public NavigationItems = member Declarations: NavigationTopLevelDeclaration[] // Functionality to access navigable F# items. +[] module public Navigation = val internal empty: NavigationItems val getNavigation: ParsedInput -> NavigationItems @@ -118,5 +119,6 @@ type NavigableItem = Kind: NavigableItemKind Container: NavigableContainer } +[] module public NavigateTo = val GetNavigableItems: ParsedInput -> NavigableItem[] diff --git a/src/Compiler/Symbols/SymbolPatterns.fs b/src/Compiler/Symbols/SymbolPatterns.fs index c29b4709244..527c628cf07 100644 --- a/src/Compiler/Symbols/SymbolPatterns.fs +++ b/src/Compiler/Symbols/SymbolPatterns.fs @@ -2,6 +2,7 @@ namespace FSharp.Compiler.Symbols + open FSharp.Compiler.Syntax /// Patterns over FSharpSymbol and derivatives. diff --git a/src/Compiler/Symbols/SymbolPatterns.fsi b/src/Compiler/Symbols/SymbolPatterns.fsi index b2635c57d4b..6fda47ae960 100644 --- a/src/Compiler/Symbols/SymbolPatterns.fsi +++ b/src/Compiler/Symbols/SymbolPatterns.fsi @@ -4,6 +4,7 @@ namespace FSharp.Compiler.Symbols /// Patterns over FSharpSymbol and derivatives. [] +[] module public FSharpSymbolPatterns = val (|AbbreviatedType|_|): FSharpEntity -> FSharpType option diff --git a/src/Compiler/SyntaxTree/ParseHelpers.fsi b/src/Compiler/SyntaxTree/ParseHelpers.fsi index bc0dc9f36fe..aae952d210c 100644 --- a/src/Compiler/SyntaxTree/ParseHelpers.fsi +++ b/src/Compiler/SyntaxTree/ParseHelpers.fsi @@ -47,6 +47,7 @@ type LexerIfdefStackEntries = (LexerIfdefStackEntry * range) list type LexerIfdefStack = LexerIfdefStackEntries +[] type LexerEndlineContinuation = | Token | IfdefSkip of int * range: range diff --git a/src/Compiler/SyntaxTree/PrettyNaming.fsi b/src/Compiler/SyntaxTree/PrettyNaming.fsi index 1156aa37f08..5ef8a02bc3d 100644 --- a/src/Compiler/SyntaxTree/PrettyNaming.fsi +++ b/src/Compiler/SyntaxTree/PrettyNaming.fsi @@ -272,6 +272,7 @@ val internal mkExceptionFieldName: (int -> string) /// The prefix of the names used for the fake namespace path added to all dynamic code entries in FSI.EXE val FsiDynamicModulePrefix: string +[] module internal CustomOperations = [] val Into: string = "into" diff --git a/src/Compiler/SyntaxTree/WarnScopes.fsi b/src/Compiler/SyntaxTree/WarnScopes.fsi index f6ae779da51..3e43b30c056 100644 --- a/src/Compiler/SyntaxTree/WarnScopes.fsi +++ b/src/Compiler/SyntaxTree/WarnScopes.fsi @@ -7,6 +7,7 @@ open FSharp.Compiler.SyntaxTrivia open FSharp.Compiler.Text open FSharp.Compiler.UnicodeLexing +[] module internal WarnScopes = /// To be called during lexing to save #nowarn / #warnon directives. diff --git a/src/Compiler/SyntaxTree/XmlDoc.fs b/src/Compiler/SyntaxTree/XmlDoc.fs index 3207b89905c..b3ef13d7a4c 100644 --- a/src/Compiler/SyntaxTree/XmlDoc.fs +++ b/src/Compiler/SyntaxTree/XmlDoc.fs @@ -15,7 +15,6 @@ open FSharp.Compiler.Text open FSharp.Compiler.Text.Range /// Represents collected XmlDoc lines -[] type XmlDoc(unprocessedLines: string[], range: range) = let rec processLines (lines: string list) = match lines with diff --git a/src/Compiler/TypedTree/TypedTreeOps.FreeVars.fsi b/src/Compiler/TypedTree/TypedTreeOps.FreeVars.fsi index 1787715a6e3..5024964297e 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.FreeVars.fsi +++ b/src/Compiler/TypedTree/TypedTreeOps.FreeVars.fsi @@ -255,6 +255,7 @@ module internal MemberRepresentation = /// for example, `seq` instead of `int list seq` | TopLevelPrefix of nested: GenericParameterStyle + [] type DisplayEnv = { includeStaticParametersInTypeNames: bool diff --git a/src/Compiler/TypedTree/TypedTreeOps.Transforms.fsi b/src/Compiler/TypedTree/TypedTreeOps.Transforms.fsi index c25d155d2cc..109cb403c38 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.Transforms.fsi +++ b/src/Compiler/TypedTree/TypedTreeOps.Transforms.fsi @@ -148,6 +148,7 @@ module internal TypeTestsAndPatterns = [] module internal Rewriting = + [] type ExprRewritingEnv = { PreIntercept: ((Expr -> Expr) -> Expr -> Expr option) option PostTransform: Expr -> Expr option diff --git a/src/Compiler/TypedTree/WellKnownAttribs.fs b/src/Compiler/TypedTree/WellKnownAttribs.fs index c05f0207551..fac3508a56e 100644 --- a/src/Compiler/TypedTree/WellKnownAttribs.fs +++ b/src/Compiler/TypedTree/WellKnownAttribs.fs @@ -118,6 +118,19 @@ type internal WellKnownValAttributes = | TailCallAttribute = (1uL <<< 40) | NotComputed = (1uL <<< 63) +module internal Flags = + let inline private bits (f: ^F when ^F: enum) = LanguagePrimitives.EnumToValue f + let inline private ofBits<'F when 'F: enum> (v: uint64) : 'F = LanguagePrimitives.EnumOfValue v + + let inline isEmpty (flags: 'F when 'F: enum) = bits flags = 0uL + let inline union (a: 'F when 'F: enum) (b: 'F) : 'F = ofBits<'F> (bits a ||| bits b) + let inline intersect (other: 'F when 'F: enum) (flags: 'F) : 'F = ofBits<'F> (bits flags &&& bits other) + let inline except (b: 'F when 'F: enum) (a: 'F) : 'F = ofBits<'F> (bits a &&& ~~~(bits b)) + let inline intersects (other: 'F when 'F: enum) (flags: 'F) = bits flags &&& bits other <> 0uL + + let inline isSubsetOf (superset: 'F when 'F: enum) (subset: 'F) = + bits subset &&& ~~~(bits superset) = 0uL + /// Generic wrapper for an item list together with cached well-known attribute flags. /// Used for O(1) lookup of well-known attributes on entities and vals. [] diff --git a/src/Compiler/TypedTree/WellKnownAttribs.fsi b/src/Compiler/TypedTree/WellKnownAttribs.fsi index 146ce3736a2..06f0d2e20cf 100644 --- a/src/Compiler/TypedTree/WellKnownAttribs.fsi +++ b/src/Compiler/TypedTree/WellKnownAttribs.fsi @@ -116,6 +116,23 @@ type internal WellKnownValAttributes = | TailCallAttribute = (1uL <<< 40) | NotComputed = (1uL <<< 63) +module internal Flags = + val inline isEmpty<'F when 'F: enum> : flags: 'F -> bool + + val inline union<'F when 'F: enum> : a: 'F -> b: 'F -> 'F + + /// Pipe-friendly: `flags |> Flags.intersect scope`. + val inline intersect<'F when 'F: enum> : other: 'F -> flags: 'F -> 'F + + /// Pipe-friendly: `a |> Flags.except b`. + val inline except<'F when 'F: enum> : b: 'F -> a: 'F -> 'F + + /// Pipe-friendly: `flags |> Flags.intersects mask`. + val inline intersects<'F when 'F: enum> : other: 'F -> flags: 'F -> bool + + /// Pipe-friendly: `subset |> Flags.isSubsetOf superset`. + val inline isSubsetOf<'F when 'F: enum> : superset: 'F -> subset: 'F -> bool + /// Generic wrapper for an item list together with cached well-known attribute flags. /// Used for O(1) lookup of well-known attributes on entities and vals. [] diff --git a/src/Compiler/Utilities/FileSystem.fs b/src/Compiler/Utilities/FileSystem.fs index 5ff6b6e180e..0265454f117 100644 --- a/src/Compiler/Utilities/FileSystem.fs +++ b/src/Compiler/Utilities/FileSystem.fs @@ -46,7 +46,6 @@ module internal Bytes = let stringAsUnicodeNullTerminated (s: string) = Array.append (Encoding.Unicode.GetBytes s) (ofInt32Array [| 0x0; 0x0 |]) -[] [] type ByteMemory() = abstract Item: int -> byte with get, set @@ -446,14 +445,12 @@ module internal FileSystemUtils = let isDll fileName = checkSuffix fileName ".dll" -[] type IAssemblyLoader = abstract AssemblyLoadFrom: fileName: string -> Assembly abstract AssemblyLoad: assemblyName: AssemblyName -> Assembly -[] type DefaultAssemblyLoader() = interface IAssemblyLoader with @@ -462,7 +459,6 @@ type DefaultAssemblyLoader() = member _.AssemblyLoad(assemblyName: AssemblyName) = Assembly.Load assemblyName -[] type IFileSystem = // note: do not add members if you can put generic implementation under StreamExtensions below. @@ -512,7 +508,6 @@ type IFileSystem = // note: do not add members if you can put generic implementation under StreamExtensions below. -[] type DefaultFileSystem() as this = abstract AssemblyLoader: IAssemblyLoader default _.AssemblyLoader = DefaultAssemblyLoader() :> IAssemblyLoader diff --git a/src/Compiler/Utilities/FileSystem.fsi b/src/Compiler/Utilities/FileSystem.fsi index a41460e49c2..d17b43ff17d 100644 --- a/src/Compiler/Utilities/FileSystem.fsi +++ b/src/Compiler/Utilities/FileSystem.fsi @@ -101,6 +101,7 @@ module internal MemoryMappedFileExtensions = static member TryFromMemory: bytes: ReadOnlyMemory -> MemoryMappedFile option /// Filesystem helpers +[] module internal FileSystemUtils = val checkPathForIllegalChars: (string -> unit) diff --git a/src/Compiler/Utilities/sformat.fsi b/src/Compiler/Utilities/sformat.fsi index 64f8d917a13..224452b7ee4 100644 --- a/src/Compiler/Utilities/sformat.fsi +++ b/src/Compiler/Utilities/sformat.fsi @@ -215,6 +215,7 @@ module internal TaggedText = val internal keywordReturn: TaggedText val internal punctuationUnit: TaggedText +[] type internal IEnvironment = /// Return to the layout-generation /// environment to layout any otherwise uninterpreted object diff --git a/src/Compiler/xlf/FSComp.txt.cs.xlf b/src/Compiler/xlf/FSComp.txt.cs.xlf index 10fe84e6ab1..84e5e3cc50c 100644 --- a/src/Compiler/xlf/FSComp.txt.cs.xlf +++ b/src/Compiler/xlf/FSComp.txt.cs.xlf @@ -402,6 +402,11 @@ Error when invalid declarations are used in type definitions. + + error (rather than warning) when an enforced compiler-semantic attribute is present in the .fs but missing from the .fsi + error (rather than warning) when an enforced compiler-semantic attribute is present in the .fs but missing from the .fsi + + Error reporting on static classes Vytváření sestav chyb u statických tříd @@ -802,6 +807,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, which takes precedence for tooling and consumers. Add the attribute to the signature, to ensure the attribute is not ignored by the compiler. + The attribute '{0}' is present on '{1}' 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. + + 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 +8982,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 + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSComp.txt.de.xlf b/src/Compiler/xlf/FSComp.txt.de.xlf index 17f93260936..d6edecb4def 100644 --- a/src/Compiler/xlf/FSComp.txt.de.xlf +++ b/src/Compiler/xlf/FSComp.txt.de.xlf @@ -402,6 +402,11 @@ Error when invalid declarations are used in type definitions. + + error (rather than warning) when an enforced compiler-semantic attribute is present in the .fs but missing from the .fsi + error (rather than warning) when an enforced compiler-semantic attribute is present in the .fs but missing from the .fsi + + Error reporting on static classes Fehlerberichterstattung für statische Klassen @@ -802,6 +807,11 @@ Das fortsetzbare Codekonstrukt "{0}" darf nur in Inlinecode verwendet werden, der durch "if __useResumableCode then..." geschützt wird. Die Gesamtkomposition muss einen gültigen fortsetzbaren Code bilden. + + The attribute '{0}' is present on '{1}' 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. + The attribute '{0}' is present on '{1}' 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. + + The 'InlineIfLambda' attribute is present in the signature but not the implementation. Das Attribut "InlineIfLambda" ist in der Signatur vorhanden, jedoch nicht in der Implementierung. @@ -8972,21 +8982,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 + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSComp.txt.es.xlf b/src/Compiler/xlf/FSComp.txt.es.xlf index ecc7e4a3f27..46573e801f1 100644 --- a/src/Compiler/xlf/FSComp.txt.es.xlf +++ b/src/Compiler/xlf/FSComp.txt.es.xlf @@ -402,6 +402,11 @@ Error when invalid declarations are used in type definitions. + + error (rather than warning) when an enforced compiler-semantic attribute is present in the .fs but missing from the .fsi + error (rather than warning) when an enforced compiler-semantic attribute is present in the .fs but missing from the .fsi + + Error reporting on static classes Informe de errores en clases estáticas @@ -802,6 +807,11 @@ La construcción de código reanudable "{0}" solo se puede usar en el código insertado protegido por "if __useResumableCode then ..." y la composición general debe formar un código reanudable válido. + + The attribute '{0}' is present on '{1}' 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. + The attribute '{0}' is present on '{1}' 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. + + The 'InlineIfLambda' attribute is present in the signature but not the implementation. El atributo "InlineIfLambda" está presente en la firma, pero no en la implementación. @@ -8972,21 +8982,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 + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSComp.txt.fr.xlf b/src/Compiler/xlf/FSComp.txt.fr.xlf index 3e0ab156a68..1acfc469b5b 100644 --- a/src/Compiler/xlf/FSComp.txt.fr.xlf +++ b/src/Compiler/xlf/FSComp.txt.fr.xlf @@ -402,6 +402,11 @@ Error when invalid declarations are used in type definitions. + + error (rather than warning) when an enforced compiler-semantic attribute is present in the .fs but missing from the .fsi + error (rather than warning) when an enforced compiler-semantic attribute is present in the .fs but missing from the .fsi + + Error reporting on static classes Rapport d’erreurs sur les classes statiques @@ -802,6 +807,11 @@ La construction de code pouvant être repris «{0}» ne peut être utilisée que dans du code inlined protégé par «if __useResumableCode then ...» et la composition globale doit former un code pouvant être repris valide. + + The attribute '{0}' is present on '{1}' 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. + The attribute '{0}' is present on '{1}' 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. + + The 'InlineIfLambda' attribute is present in the signature but not the implementation. L’attribut « InlineIfLambda » est présent dans la signature, mais pas dans l’implémentation. @@ -8972,21 +8982,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 + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSComp.txt.it.xlf b/src/Compiler/xlf/FSComp.txt.it.xlf index 424a8e0310b..deaca46fad0 100644 --- a/src/Compiler/xlf/FSComp.txt.it.xlf +++ b/src/Compiler/xlf/FSComp.txt.it.xlf @@ -402,6 +402,11 @@ Error when invalid declarations are used in type definitions. + + error (rather than warning) when an enforced compiler-semantic attribute is present in the .fs but missing from the .fsi + error (rather than warning) when an enforced compiler-semantic attribute is present in the .fs but missing from the .fsi + + Error reporting on static classes Segnalazione errori nelle classi statiche @@ -802,6 +807,11 @@ Il costrutto di codice ripristinabile '{0}' può essere usato solo nel codice impostato come inline e protetto da 'if __useResumableCode then...' e l'intera composizione deve formare codice ripristinabile valido. + + The attribute '{0}' is present on '{1}' 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. + The attribute '{0}' is present on '{1}' 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. + + The 'InlineIfLambda' attribute is present in the signature but not the implementation. L'attributo 'InlineIfLambda' è presente nella firma, ma non nell'implementazione. @@ -8972,21 +8982,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 + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSComp.txt.ja.xlf b/src/Compiler/xlf/FSComp.txt.ja.xlf index d588b1908d7..10029fab134 100644 --- a/src/Compiler/xlf/FSComp.txt.ja.xlf +++ b/src/Compiler/xlf/FSComp.txt.ja.xlf @@ -402,6 +402,11 @@ Error when invalid declarations are used in type definitions. + + error (rather than warning) when an enforced compiler-semantic attribute is present in the .fs but missing from the .fsi + error (rather than warning) when an enforced compiler-semantic attribute is present in the .fs but missing from the .fsi + + Error reporting on static classes 静的クラスに関するエラー報告 @@ -802,6 +807,11 @@ 再開可能なコード コンストラクト '{0}' は、 'if __useResumableCode then ...' によって保護されているインライン コードでのみ使用でき、コンポジション全体は有効な再開可能コードを形成する必要があります。 + + The attribute '{0}' is present on '{1}' 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. + The attribute '{0}' is present on '{1}' 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. + + The 'InlineIfLambda' attribute is present in the signature but not the implementation. 'InlineIfLambda' 属性はシグネチャに存在しますが、実装はありません。 @@ -8972,21 +8982,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 + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSComp.txt.ko.xlf b/src/Compiler/xlf/FSComp.txt.ko.xlf index ec0d939e7b2..1d7a19af957 100644 --- a/src/Compiler/xlf/FSComp.txt.ko.xlf +++ b/src/Compiler/xlf/FSComp.txt.ko.xlf @@ -402,6 +402,11 @@ Error when invalid declarations are used in type definitions. + + error (rather than warning) when an enforced compiler-semantic attribute is present in the .fs but missing from the .fsi + error (rather than warning) when an enforced compiler-semantic attribute is present in the .fs but missing from the .fsi + + Error reporting on static classes 정적 클래스에 대한 오류 보고 @@ -802,6 +807,11 @@ 다시 시작 가능한 코드 구문 '{0}'은 'if __useResumableCode then ...'로 보호되는 인라인 코드에서만 사용할 수 있습니다. 전반적인 구성은 유효한 다시 시작 가능한 코드를 형성해야 합니다. + + The attribute '{0}' is present on '{1}' 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. + The attribute '{0}' is present on '{1}' 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. + + The 'InlineIfLambda' attribute is present in the signature but not the implementation. 'InlineIfLambda' 특성이 서명에 있지만 구현에는 없습니다. @@ -8972,21 +8982,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 + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSComp.txt.pl.xlf b/src/Compiler/xlf/FSComp.txt.pl.xlf index ae93051caa0..170e6e37718 100644 --- a/src/Compiler/xlf/FSComp.txt.pl.xlf +++ b/src/Compiler/xlf/FSComp.txt.pl.xlf @@ -402,6 +402,11 @@ Error when invalid declarations are used in type definitions. + + error (rather than warning) when an enforced compiler-semantic attribute is present in the .fs but missing from the .fsi + error (rather than warning) when an enforced compiler-semantic attribute is present in the .fs but missing from the .fsi + + Error reporting on static classes Raportowanie błędów dla klas statycznych @@ -802,6 +807,11 @@ Konstrukcja kodu z możliwością wznowienia "{0}" może być używana tylko w nieliniowym kodzie chronionym przez "If __useResumableCode then..." i ogólna kompozycja musi być w formacie prawidłowego kodu z możliwością wznowienia. + + The attribute '{0}' is present on '{1}' 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. + The attribute '{0}' is present on '{1}' 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. + + The 'InlineIfLambda' attribute is present in the signature but not the implementation. Atrybut "InlineIfLambda" jest obecny w sygnaturze, ale nie w implementacji. @@ -8972,21 +8982,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 + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf index 0e0c2367513..87ef255a7e8 100644 --- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf +++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf @@ -402,6 +402,11 @@ Error when invalid declarations are used in type definitions. + + error (rather than warning) when an enforced compiler-semantic attribute is present in the .fs but missing from the .fsi + error (rather than warning) when an enforced compiler-semantic attribute is present in the .fs but missing from the .fsi + + Error reporting on static classes Relatório de erros em classes estáticas @@ -802,6 +807,11 @@ A construção de código retomável '{0}' só pode ser usada em código delimitado protegido por 'se __useResumableCode então ...' e a composição geral deve formar um código retomável válido. + + The attribute '{0}' is present on '{1}' 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. + The attribute '{0}' is present on '{1}' 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. + + The 'InlineIfLambda' attribute is present in the signature but not the implementation. O atributo 'InlineIfLambda' está presente na assinatura, mas não na implementação. @@ -8972,21 +8982,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 + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSComp.txt.ru.xlf b/src/Compiler/xlf/FSComp.txt.ru.xlf index e25503d865e..57f29e98517 100644 --- a/src/Compiler/xlf/FSComp.txt.ru.xlf +++ b/src/Compiler/xlf/FSComp.txt.ru.xlf @@ -402,6 +402,11 @@ Error when invalid declarations are used in type definitions. + + error (rather than warning) when an enforced compiler-semantic attribute is present in the .fs but missing from the .fsi + error (rather than warning) when an enforced compiler-semantic attribute is present in the .fs but missing from the .fsi + + Error reporting on static classes Отчеты об ошибках для статических классов @@ -802,6 +807,11 @@ Конструкцию возобновляемого кода "{0}" можно использовать только во встроенном коде, защищенном с помощью "if __useResumableCode then ...", а общая композиция должна представлять собой допустимый возобновляемый код. + + The attribute '{0}' is present on '{1}' 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. + The attribute '{0}' is present on '{1}' 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. + + The 'InlineIfLambda' attribute is present in the signature but not the implementation. Атрибут "InlineIfLambda" присутствует в сигнатуре, но отсутствует в реализации. @@ -8972,21 +8982,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 + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSComp.txt.tr.xlf b/src/Compiler/xlf/FSComp.txt.tr.xlf index a296c02e44c..08753e47a27 100644 --- a/src/Compiler/xlf/FSComp.txt.tr.xlf +++ b/src/Compiler/xlf/FSComp.txt.tr.xlf @@ -402,6 +402,11 @@ Error when invalid declarations are used in type definitions. + + error (rather than warning) when an enforced compiler-semantic attribute is present in the .fs but missing from the .fsi + error (rather than warning) when an enforced compiler-semantic attribute is present in the .fs but missing from the .fsi + + Error reporting on static classes Statik sınıflarda hata bildirimi @@ -802,6 +807,11 @@ Sürdürülebilir kod yapısı '{0}' yalnızca 'if__useResumableCode then ...' tarafından korunan satır içine alınmış kodda kullanılabilir ve genel birleştirme geçerli sürdürülebilir kod biçiminde olmalıdır. + + The attribute '{0}' is present on '{1}' 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. + The attribute '{0}' is present on '{1}' 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. + + The 'InlineIfLambda' attribute is present in the signature but not the implementation. 'InlineIfLambda' özniteliği imzada var ama uygulamada yok. @@ -8972,21 +8982,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 + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf index aa66fa326f6..e117ab0ec5d 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf @@ -402,6 +402,11 @@ Error when invalid declarations are used in type definitions. + + error (rather than warning) when an enforced compiler-semantic attribute is present in the .fs but missing from the .fsi + error (rather than warning) when an enforced compiler-semantic attribute is present in the .fs but missing from the .fsi + + Error reporting on static classes 有关静态类的错误报告 @@ -802,6 +807,11 @@ 可恢复的代码构造 "{0}" 只能用于受 "if __useResumableCode then..." 保护的内联代码,且整体组合必须构成有效的可恢复代码。 + + The attribute '{0}' is present on '{1}' 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. + The attribute '{0}' is present on '{1}' 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. + + The 'InlineIfLambda' attribute is present in the signature but not the implementation. "InlineIfLambda" 属性存在于签名中,但实现中不存在。 @@ -8972,21 +8982,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 + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf index d8517bff3ff..31c5596a098 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf @@ -402,6 +402,11 @@ Error when invalid declarations are used in type definitions. + + error (rather than warning) when an enforced compiler-semantic attribute is present in the .fs but missing from the .fsi + error (rather than warning) when an enforced compiler-semantic attribute is present in the .fs but missing from the .fsi + + Error reporting on static classes 報告靜態類別時發生錯誤 @@ -802,6 +807,11 @@ 可繼續的程式碼構造 '{0}' 只能用於 'if __useResumableCode then ...' 所保護的內嵌程式碼中,且整體組合必須形成有效的可繼續程式碼。 + + The attribute '{0}' is present on '{1}' 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. + The attribute '{0}' is present on '{1}' 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. + + The 'InlineIfLambda' attribute is present in the signature but not the implementation. 'InlineIfLambda' 屬性存在於簽章中,但不存在於實作中。 @@ -8972,21 +8982,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 + + \ No newline at end of file diff --git a/src/FSharp.Core/Query.fsi b/src/FSharp.Core/Query.fsi index c0774328cd6..9faefac1e95 100644 --- a/src/FSharp.Core/Query.fsi +++ b/src/FSharp.Core/Query.fsi @@ -428,6 +428,7 @@ namespace Microsoft.FSharp.Linq.QueryRunExtensions /// /// Contains modules used to support the F# query syntax. /// + [] module LowPriority = type Microsoft.FSharp.Linq.QueryBuilder with /// @@ -439,6 +440,7 @@ namespace Microsoft.FSharp.Linq.QueryRunExtensions /// /// A module used to support the F# query syntax. /// + [] module HighPriority = type Microsoft.FSharp.Linq.QueryBuilder with /// diff --git a/src/FSharp.Core/async.fsi b/src/FSharp.Core/async.fsi index b2fe66ddd13..2e99fea7c65 100644 --- a/src/FSharp.Core/async.fsi +++ b/src/FSharp.Core/async.fsi @@ -1077,6 +1077,7 @@ namespace Microsoft.FSharp.Control /// The F# compiler emits references to this type to implement F# async expressions. /// /// Async Internals + [] type AsyncReturn /// The F# compiler emits references to this type to implement F# async expressions. diff --git a/src/FSharp.Core/fslib-extra-pervasives.fsi b/src/FSharp.Core/fslib-extra-pervasives.fsi index 1e38995a2bd..dff6b2e68d1 100644 --- a/src/FSharp.Core/fslib-extra-pervasives.fsi +++ b/src/FSharp.Core/fslib-extra-pervasives.fsi @@ -375,12 +375,15 @@ namespace Microsoft.FSharp.Core.CompilerServices /// Library functionality for supporting type providers and code generated by the F# compiler. See /// also F# Type Providers in the F# Language Guide. /// + [] type MeasureProduct<'Measure1, 'Measure2> /// Represents the inverse of a measure expressions when returned as a generic argument of a provided type. + [] type MeasureInverse<'Measure> /// Represents the '1' measure expression when returned as a generic argument of a provided type. + [] type MeasureOne /// Place on a class that implements ITypeProvider to extend the compiler diff --git a/src/FSharp.Core/nativeptr.fsi b/src/FSharp.Core/nativeptr.fsi index 2f380f85dae..798290f1d4e 100644 --- a/src/FSharp.Core/nativeptr.fsi +++ b/src/FSharp.Core/nativeptr.fsi @@ -25,6 +25,7 @@ module NativePtr = /// [] [] + [] val inline ofNativeInt: address: nativeint -> nativeptr<'T> /// Returns a machine address for a given typed native pointer. @@ -36,6 +37,7 @@ module NativePtr = /// [] [] + [] val inline toNativeInt: address: nativeptr<'T> -> nativeint /// Returns a typed native pointer for a untyped native pointer. @@ -47,6 +49,7 @@ module NativePtr = /// [] [] + [] val inline ofVoidPtr: address: voidptr -> nativeptr<'T> /// Returns an untyped native pointer for a given typed native pointer. @@ -58,6 +61,7 @@ module NativePtr = /// [] [] + [] val inline toVoidPtr: address: nativeptr<'T> -> voidptr /// Returns a typed native pointer for a Common IL (Intermediate Language) signature pointer. @@ -69,6 +73,7 @@ module NativePtr = /// [] [] + [] val inline ofILSigPtr: address: ilsigptr<'T> -> nativeptr<'T> /// Returns a Common IL (Intermediate Language) signature pointer for a given typed native pointer. @@ -80,6 +85,7 @@ module NativePtr = /// [] [] + [] val inline toILSigPtr: address: nativeptr<'T> -> ilsigptr<'T> /// Converts a given typed native pointer to a managed pointer. @@ -91,6 +97,7 @@ module NativePtr = /// [] [] + [] val inline toByRef: address: nativeptr<'T> -> byref<'T> /// Returns a typed native pointer by adding index * sizeof<'T> to the @@ -104,6 +111,7 @@ module NativePtr = /// [] [] + [] val inline add: address: nativeptr<'T> -> index: int -> nativeptr<'T> /// Dereferences the typed native pointer computed by adding index * sizeof<'T> to the @@ -117,6 +125,7 @@ module NativePtr = /// [] [] + [] val inline get: address: nativeptr<'T> -> index: int -> 'T /// Dereferences the given typed native pointer. @@ -128,6 +137,7 @@ module NativePtr = /// [] [] + [] val inline read: address: nativeptr<'T> -> 'T /// Assigns the value into the memory location referenced by the given typed native pointer. @@ -138,6 +148,7 @@ module NativePtr = /// [] [] + [] val inline write: address: nativeptr<'T> -> value: 'T -> unit /// Assigns the value into the memory location referenced by the typed native @@ -150,6 +161,7 @@ module NativePtr = /// [] [] + [] val inline set: address: nativeptr<'T> -> index: int -> value: 'T -> unit /// Allocates a region of memory on the stack. @@ -161,6 +173,7 @@ module NativePtr = /// [] [] + [] val inline stackalloc: count: int -> nativeptr<'T> /// Gets the null native pointer. @@ -171,6 +184,7 @@ module NativePtr = [] [] [] + [] val inline nullPtr<'T when 'T: unmanaged> : nativeptr<'T> /// Tests whether the given native pointer is null. @@ -182,6 +196,7 @@ module NativePtr = /// [] [] + [] val inline isNullPtr: address: nativeptr<'T> -> bool /// Clears the value stored at the location of a given native pointer. @@ -191,6 +206,7 @@ module NativePtr = /// [] [] + [] val inline clear: address: nativeptr<'T> -> unit /// Initializes a specified block of memory starting at a specific address to a given byte count and initial byte value. @@ -202,6 +218,7 @@ module NativePtr = /// [] [] + [] val inline initBlock: address: nativeptr<'T> -> value: byte -> count: uint32 -> unit /// Copies a value to a specified destination address from a specified source address. @@ -212,6 +229,7 @@ module NativePtr = /// [] [] + [] val inline copy: destination: nativeptr<'T> -> source: nativeptr<'T> -> unit /// Copies a block of memory to a specified destination address starting from a specified source address until a specified byte count of (count * sizeof<'T>). @@ -223,4 +241,5 @@ module NativePtr = /// [] [] + [] val inline copyBlock: destination: nativeptr<'T> -> source: nativeptr<'T> -> count: int -> unit diff --git a/src/FSharp.Core/prim-types.fsi b/src/FSharp.Core/prim-types.fsi index fb03e49d201..82ab0584ca8 100644 --- a/src/FSharp.Core/prim-types.fsi +++ b/src/FSharp.Core/prim-types.fsi @@ -1233,14 +1233,17 @@ namespace Microsoft.FSharp.Core /// Represents a byref that can be written [] + [] type Out /// Represents a byref that can be read [] + [] type In /// Represents a byref that can be both read and written [] + [] type InOut /// Represents a in-argument or readonly managed pointer in F# code. This type should only be used with F# 4.5+. @@ -1745,6 +1748,7 @@ namespace Microsoft.FSharp.Core /// The input object. /// /// The managed pointer. + [] val inline (~&): obj: 'T -> byref<'T> /// Address-of. Uses of this value may result in the generation of unverifiable code. @@ -1752,6 +1756,7 @@ namespace Microsoft.FSharp.Core /// The input object. /// /// The unmanaged pointer. + [] val inline (~&&): obj: 'T -> nativeptr<'T> //------------------------------------------------------------------------- @@ -2773,6 +2778,7 @@ namespace Microsoft.FSharp.Core /// /// /// + [] val inline (~-): n: ^T -> ^T when ^T: (static member ( ~- ): ^T -> ^T) and default ^T: int /// Overloaded addition operator @@ -2803,6 +2809,7 @@ namespace Microsoft.FSharp.Core /// 10 - 2 // Evaluates to 8 /// /// + [] val inline (-): x: ^T1 -> y: ^T2 -> ^T3 when (^T1 or ^T2): (static member (-): ^T1 * ^T2 -> ^T3) and default ^T2: ^T3 and default ^T3: ^T1 and default ^T3: ^T2 and default ^T1: ^T3 and default ^T1: ^T2 and default ^T1: int /// Overloaded multiplication operator @@ -2831,6 +2838,7 @@ namespace Microsoft.FSharp.Core /// 16 / 2 // Evaluates to 8 /// /// + [] val inline (/): x: ^T1 -> y: ^T2 -> ^T3 when (^T1 or ^T2): (static member (/): ^T1 * ^T2 -> ^T3) and default ^T2: ^T3 and default ^T3: ^T1 and default ^T3: ^T2 and default ^T1: ^T3 and default ^T1: ^T2 and default ^T1: int /// Overloaded modulo operator @@ -2845,6 +2853,7 @@ namespace Microsoft.FSharp.Core /// 29 % 5 // Evaluates to 4 /// /// + [] val inline (%): x: ^T1 -> y: ^T2 -> ^T3 when (^T1 or ^T2): (static member (%): ^T1 * ^T2 -> ^T3) and default ^T2: ^T3 and default ^T3: ^T1 and default ^T3: ^T2 and default ^T1: ^T3 and default ^T1: ^T2 and default ^T1: int /// Overloaded bitwise-AND operator @@ -2862,6 +2871,7 @@ namespace Microsoft.FSharp.Core /// /// Evaluates to 9 /// + [] val inline (&&&): x: ^T -> y: ^T -> ^T when ^T: (static member (&&&): ^T * ^T -> ^T) and default ^T: int /// Overloaded bitwise-OR operator @@ -2879,6 +2889,7 @@ namespace Microsoft.FSharp.Core /// /// Evaluates to 15 /// + [] val inline (|||): x: ^T -> y: ^T -> ^T when ^T: (static member (|||): ^T * ^T -> ^T) and default ^T: int /// Overloaded bitwise-XOR operator @@ -2896,6 +2907,7 @@ namespace Microsoft.FSharp.Core /// /// Evaluates to 6 /// + [] val inline (^^^): x: ^T -> y: ^T -> ^T when ^T: (static member (^^^): ^T * ^T -> ^T) and default ^T: int /// Overloaded byte-shift left operator by a specified number of bits @@ -2912,6 +2924,7 @@ namespace Microsoft.FSharp.Core /// /// Evaluates to 208 /// + [] val inline (<<<): value: ^T -> shift: int32 -> ^T when ^T : (static member (<<<) : ^T * int32 -> ^T) and default ^T : int /// Overloaded byte-shift right operator by a specified number of bits @@ -2930,6 +2943,7 @@ namespace Microsoft.FSharp.Core /// Evaluates to 3 /// /// + [] val inline (>>>): value: ^T -> shift: int32 -> ^T when ^T: (static member (>>>): ^T * int32 -> ^T) and default ^T: int /// Overloaded bitwise-NOT operator @@ -2946,6 +2960,7 @@ namespace Microsoft.FSharp.Core /// Evaluates to 195 /// /// + [] val inline (~~~): value: ^T -> ^T when ^T: (static member (~~~): ^T -> ^T) and default ^T: int /// Overloaded prefix-plus operator @@ -2956,6 +2971,7 @@ namespace Microsoft.FSharp.Core /// /// /// + [] val inline (~+): value: ^T -> ^T when ^T: (static member (~+): ^T -> ^T) and default ^T: int /// Structural less-than comparison @@ -3289,6 +3305,7 @@ namespace Microsoft.FSharp.Core /// The result value. [] [] + [] val inline rethrow: unit -> 'T /// Rethrows an exception. This should only be used when handling an exception @@ -3310,6 +3327,7 @@ namespace Microsoft.FSharp.Core /// /// [] + [] val inline reraise: unit -> 'T /// Builds a object. @@ -4493,6 +4511,7 @@ namespace Microsoft.FSharp.Core /// /// [] + [] val inline byte: value: ^T -> byte when ^T: (static member op_Explicit: ^T -> byte) and default ^T: int /// Converts the argument to signed byte. This is a direct conversion for all @@ -4513,6 +4532,7 @@ namespace Microsoft.FSharp.Core /// /// [] + [] val inline sbyte: value:^T -> sbyte when ^T: (static member op_Explicit: ^T -> sbyte) and default ^T: int /// Converts the argument to signed 16-bit integer. This is a direct conversion for all @@ -4533,6 +4553,7 @@ namespace Microsoft.FSharp.Core /// /// [] + [] val inline int16: value: ^T -> int16 when ^T: (static member op_Explicit: ^T -> int16) and default ^T: int /// Converts the argument to unsigned 16-bit integer. This is a direct conversion for all @@ -4553,6 +4574,7 @@ namespace Microsoft.FSharp.Core /// /// [] + [] val inline uint16: value: ^T -> uint16 when ^T: (static member op_Explicit: ^T -> uint16) and default ^T: int /// Converts the argument to signed 32-bit integer. This is a direct conversion for all @@ -4632,6 +4654,7 @@ namespace Microsoft.FSharp.Core /// /// [] + [] val inline int32: value: ^T -> int32 when ^T: (static member op_Explicit: ^T -> int32) and default ^T: int /// Converts the argument to unsigned 32-bit integer. This is a direct conversion for all @@ -4652,6 +4675,7 @@ namespace Microsoft.FSharp.Core /// /// [] + [] val inline uint32: value: ^T -> uint32 when ^T: (static member op_Explicit: ^T -> uint32) and default ^T: int /// Converts the argument to signed 64-bit integer. This is a direct conversion for all @@ -4672,6 +4696,7 @@ namespace Microsoft.FSharp.Core /// /// [] + [] val inline int64: value: ^T -> int64 when ^T : (static member op_Explicit : ^T -> int64) and default ^T : int /// Converts the argument to unsigned 64-bit integer. This is a direct conversion for all @@ -4692,6 +4717,7 @@ namespace Microsoft.FSharp.Core /// /// [] + [] val inline uint64: value: ^T -> uint64 when ^T: (static member op_Explicit: ^T -> uint64) and default ^T: int /// Converts the argument to 32-bit float. This is a direct conversion for all @@ -4712,6 +4738,7 @@ namespace Microsoft.FSharp.Core /// /// [] + [] val inline float32: value: ^T -> float32 when ^T: (static member op_Explicit: ^T -> float32) and default ^T: int /// Converts the argument to 64-bit float. This is a direct conversion for all @@ -4732,6 +4759,7 @@ namespace Microsoft.FSharp.Core /// /// [] + [] val inline float: value: ^T -> float when ^T: (static member op_Explicit: ^T -> float) and default ^T: int /// Converts the argument to signed native integer. This is a direct conversion for all @@ -4751,6 +4779,7 @@ namespace Microsoft.FSharp.Core /// /// [] + [] val inline nativeint: value: ^T -> nativeint when ^T: (static member op_Explicit: ^T -> nativeint) and default ^T: int /// Converts the argument to unsigned native integer using a direct conversion for all @@ -4770,6 +4799,7 @@ namespace Microsoft.FSharp.Core /// /// [] + [] val inline unativeint: value: ^T -> unativeint when ^T: (static member op_Explicit: ^T -> unativeint) and default ^T: int /// Converts the argument to a string using ToString. @@ -4809,6 +4839,7 @@ namespace Microsoft.FSharp.Core /// /// [] + [] val inline decimal: value: ^T -> decimal when ^T: (static member op_Explicit: ^T -> decimal) and default ^T: int /// Converts the argument to character. Numeric inputs are converted according to the UTF-16 @@ -4828,6 +4859,7 @@ namespace Microsoft.FSharp.Core /// /// [] + [] val inline char: value: ^T -> char when ^T: (static member op_Explicit: ^T -> char) and default ^T: int /// An active pattern to match values of type @@ -5929,6 +5961,7 @@ namespace Microsoft.FSharp.Core /// /// /// + [] val inline (~-): value: ^T -> ^T when ^T: (static member (~-): ^T -> ^T) and default ^T: int /// Overloaded subtraction operator (checks for overflow) @@ -5976,6 +6009,7 @@ namespace Microsoft.FSharp.Core /// /// [] + [] val inline byte: value: ^T -> byte when ^T: (static member op_Explicit: ^T -> byte) and default ^T: int /// Converts the argument to sbyte. This is a direct, checked conversion for all @@ -5990,6 +6024,7 @@ namespace Microsoft.FSharp.Core /// /// [] + [] val inline sbyte: value: ^T -> sbyte when ^T: (static member op_Explicit: ^T -> sbyte) and default ^T: int /// Converts the argument to int16. This is a direct, checked conversion for all @@ -6004,6 +6039,7 @@ namespace Microsoft.FSharp.Core /// /// [] + [] val inline int16: value: ^T -> int16 when ^T: (static member op_Explicit: ^T -> int16) and default ^T: int /// Converts the argument to uint16. This is a direct, checked conversion for all @@ -6018,6 +6054,7 @@ namespace Microsoft.FSharp.Core /// /// [] + [] val inline uint16: value: ^T -> uint16 when ^T: (static member op_Explicit: ^T -> uint16) and default ^T: int /// Converts the argument to int. This is a direct, checked conversion for all @@ -6046,6 +6083,7 @@ namespace Microsoft.FSharp.Core /// /// [] + [] val inline int32: value: ^T -> int32 when ^T: (static member op_Explicit: ^T -> int32) and default ^T: int /// Converts the argument to uint32. This is a direct, checked conversion for all @@ -6060,6 +6098,7 @@ namespace Microsoft.FSharp.Core /// /// [] + [] val inline uint32: value: ^T -> uint32 when ^T: (static member op_Explicit: ^T -> uint32) and default ^T: int /// Converts the argument to int64. This is a direct, checked conversion for all @@ -6074,6 +6113,7 @@ namespace Microsoft.FSharp.Core /// /// [] + [] val inline int64: value: ^T -> int64 when ^T: (static member op_Explicit: ^T -> int64) and default ^T: int /// Converts the argument to uint64. This is a direct, checked conversion for all @@ -6088,6 +6128,7 @@ namespace Microsoft.FSharp.Core /// /// [] + [] val inline uint64: value: ^T -> uint64 when ^T: (static member op_Explicit: ^T -> uint64) and default ^T: int /// Converts the argument to . This is a direct, checked conversion for all @@ -6101,6 +6142,7 @@ namespace Microsoft.FSharp.Core /// /// [] + [] val inline nativeint: value: ^T -> nativeint when ^T: (static member op_Explicit: ^T -> nativeint) and default ^T: int /// Converts the argument to unativeint. This is a direct, checked conversion for all @@ -6114,6 +6156,7 @@ namespace Microsoft.FSharp.Core /// /// [] + [] val inline unativeint: value: ^T -> unativeint when ^T: (static member op_Explicit: ^T -> unativeint) and default ^T: int /// Converts the argument to char. Numeric inputs are converted using a checked @@ -6128,6 +6171,7 @@ namespace Microsoft.FSharp.Core /// /// [] + [] val inline char: value: ^T -> char when ^T: (static member op_Explicit: ^T -> char) and default ^T: int namespace Microsoft.FSharp.Control diff --git a/src/FSharp.Core/resumable.fsi b/src/FSharp.Core/resumable.fsi index f5a7f7ec719..e62a6597729 100644 --- a/src/FSharp.Core/resumable.fsi +++ b/src/FSharp.Core/resumable.fsi @@ -123,6 +123,7 @@ type SetStateMachineMethodImpl<'Data> = delegate of byref = delegate of byref> -> 'Result /// Contains compiler intrinsics related to the definition of state machines. +[] module StateMachineHelpers = /// diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/Signatures/SignatureEnforcedAttributes.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/Signatures/SignatureEnforcedAttributes.fs new file mode 100644 index 00000000000..e9a7639999a --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/Signatures/SignatureEnforcedAttributes.fs @@ -0,0 +1,482 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace Conformance.Signatures + +open Xunit +open FSharp.Test +open FSharp.Test.Compiler + +module SignatureEnforcedAttributes = + + let private fsi src = SourceCodeFileKind.Create("Library.fsi", src) + let private fs src = SourceCodeFileKind.Create("Library.fs", src) + + let private compileSigImpl (sigSrc: string) (implSrc: string) = + fsFromString (fsi sigSrc) + |> FS + |> withAdditionalSourceFile (fs implSrc) + |> asLibrary + |> ignoreWarnings + |> compile + + [] + let ``NoDynamicInvocation in impl but not sig produces warning`` () = + let sigSrc = """ +module M +val inline f: x: int -> int +""" + let implSrc = """ +module M +[] +let inline f (x: int) = x + 1 +""" + compileSigImpl sigSrc implSrc + |> shouldSucceed + |> withWarningCode 3888 + |> withDiagnosticMessageMatches "NoDynamicInvocation" + + [] + let ``NoDynamicInvocation in both impl and sig compiles clean`` () = + let sigSrc = """ +module M +[] +val inline f: x: int -> int +""" + let implSrc = """ +module M +[] +let inline f (x: int) = x + 1 +""" + compileSigImpl sigSrc implSrc + |> shouldSucceed + + [] + let ``Regular attribute in impl but not sig does NOT raise enforcement error`` () = + let sigSrc = """ +module M +val f: x: int -> int +""" + let implSrc = """ +module M +[] +let f (x: int) = x + 1 +""" + // Obsolete is NOT in the enforced list - compilation must succeed. + compileSigImpl sigSrc implSrc + |> shouldSucceed + + [] + let ``InlineIfLambda in sig but not impl still raises (existing behavior preserved)`` () = + let sigSrc = """ +module M +val run: f: (int -> int) -> int +""" + let implSrc = """ +module M +let run ([] f: int -> int) = f 42 +""" + // Pre-existing FS3518 path. Must still fire. + compileSigImpl sigSrc implSrc + |> shouldFail + |> withDiagnosticMessageMatches "InlineIfLambda" + + [] + let ``Attribute absent from both impl and sig is fine`` () = + let sigSrc = """ +module M +val f: x: int -> int +""" + let implSrc = """ +module M +let f (x: int) = x + 1 +""" + compileSigImpl sigSrc implSrc + |> shouldSucceed + + [] + let ``NoDynamicInvocation on type member in impl but not sig produces warning`` () = + let sigSrc = """ +module M +type T = + new: unit -> T + member inline F: x: int -> int +""" + let implSrc = """ +module M +type T() = + [] + member inline _.F(x: int) = x + 1 +""" + compileSigImpl sigSrc implSrc + |> shouldSucceed + |> withWarningCode 3888 + |> withDiagnosticMessageMatches "NoDynamicInvocation" + + [] + let ``RequiresExplicitTypeArguments in impl but not sig produces warning`` () = + let sigSrc = """ +module M +val f: x: int -> int +""" + let implSrc = """ +module M +[] +let f (x: int) = x + 1 +""" + compileSigImpl sigSrc implSrc + |> shouldSucceed + |> withWarningCode 3888 + |> withDiagnosticMessageMatches "RequiresExplicitTypeArguments" + + [] + let ``Conditional in impl but not sig produces warning`` () = + let sigSrc = """ +module M +type T = + new: unit -> T + member F: x: int -> unit +""" + let implSrc = """ +module M +type T() = + [] + member _.F(x: int) = () +""" + compileSigImpl sigSrc implSrc + |> shouldSucceed + |> withWarningCode 3888 + |> withDiagnosticMessageMatches "Conditional" + + [] + let ``RequireQualifiedAccess on union in impl but not sig produces warning`` () = + let sigSrc = """ +module M +type U = A | B +""" + let implSrc = """ +module M +[] +type U = A | B +""" + compileSigImpl sigSrc implSrc + |> shouldSucceed + |> withWarningCode 3888 + |> withDiagnosticMessageMatches "RequireQualifiedAccess" + + [] + let ``AutoOpen on nested module in impl but not sig does NOT fire FS3888 (intentionally asymmetric)`` () = + // AutoOpen on an internal module is a legitimate asymmetric idiom: auto-open + // within the project, opaque for InternalsVisibleTo consumers. + let sigSrc = """ +module M +module Inner = + val x: int +""" + let implSrc = """ +module M +[] +module Inner = + let x = 42 +""" + compileSigImpl sigSrc implSrc + |> shouldSucceed + |> withWarningCodes [] + + [] + let ``CLIMutable on record in impl but not sig produces warning`` () = + let sigSrc = """ +module M +type R = { mutable X: int } +""" + let implSrc = """ +module M +[] +type R = { mutable X: int } +""" + compileSigImpl sigSrc implSrc + |> shouldSucceed + |> withWarningCode 3888 + |> withDiagnosticMessageMatches "CLIMutable" + + [] + let ``AllowNullLiteral on type in impl but not sig produces warning`` () = + let sigSrc = """ +module M +type C = + new: unit -> C +""" + let implSrc = """ +module M +[] +type C() = class end +""" + compileSigImpl sigSrc implSrc + |> shouldSucceed + |> withWarningCode 3888 + |> withDiagnosticMessageMatches "AllowNullLiteral" + + [] + let ``NoEquality on record in impl but not sig produces warning`` () = + // The mismatch also triggers FS293 (signature requires IStructuralEquatable + // but implementation has NoEquality). That's a separate, existing diagnostic. + // We verify the new warning is included regardless. + let sigSrc = """ +module M +type R = { X: int } +""" + let implSrc = """ +module M +[] +type R = { X: int } +""" + compileSigImpl sigSrc implSrc + |> withDiagnosticMessageMatches "NoEquality" + + [] + let ``Multiple enforced attributes on same val produce multiple warnings`` () = + let sigSrc = """ +module M +val inline f: x: int -> int +""" + let implSrc = """ +module M +[] +[] +let inline f (x: int) = x + 1 +""" + compileSigImpl sigSrc implSrc + |> shouldSucceed + |> withDiagnosticMessageMatches "NoDynamicInvocation" + |> withDiagnosticMessageMatches "RequiresExplicitTypeArguments" + + [] + let ``Warning is suppressible with nowarn 3888`` () = + let sigSrc = """ +module M +val inline f: x: int -> int +""" + let implSrc = """ +module M +#nowarn "3888" +[] +let inline f (x: int) = x + 1 +""" + fsFromString (fsi sigSrc) + |> FS + |> withAdditionalSourceFile (fs implSrc) + |> asLibrary + |> compile + |> shouldSucceed + + // Module-level attribute. + + [] + let ``AutoOpen on top-level module in impl but not sig does NOT fire FS3888 (intentionally asymmetric)`` () = + let sigSrc = """ +module M.Sub +val x: int +""" + let implSrc = """ +[] +module M.Sub +let x = 1 +""" + compileSigImpl sigSrc implSrc + |> shouldSucceed + |> withWarningCodes [] + + // Diagnostic placement and range. + + [] + let ``Diagnostic squiggle is placed on the offending attribute in the .fs`` () = + let sigSrc = """ +module M +val inline f: x: int -> int +""" + // Line numbers (1-based) — line 1 is empty, line 2 `module M`, line 3 the attribute. + let implSrc = """ +module M +[] +let inline f (x: int) = x + 1 +""" + let result = compileSigImpl sigSrc implSrc |> shouldSucceed + // Verify a single FS3888 diagnostic and that its range targets the + // attribute on line 3, not the value identifier on line 4. + let diagnostics = + match result with + | CompilationResult.Success r -> r.Diagnostics + | CompilationResult.Failure r -> r.Diagnostics + let attribDiag = + diagnostics + |> List.filter (fun d -> match d.Error with Warning n -> n = 3888 | _ -> false) + |> List.exactlyOne + Assert.Equal(3, attribDiag.Range.StartLine) + Assert.Equal(3, attribDiag.Range.EndLine) + + [] + let ``Diagnostic on entity attribute targets the attribute in the .fs`` () = + let sigSrc = """ +module M +type U = A | B +""" + let implSrc = """ +module M +[] +type U = A | B +""" + let result = compileSigImpl sigSrc implSrc |> shouldSucceed + let diagnostics = + match result with + | CompilationResult.Success r -> r.Diagnostics + | CompilationResult.Failure r -> r.Diagnostics + let attribDiag = + diagnostics + |> List.filter (fun d -> match d.Error with Warning n -> n = 3888 | _ -> false) + |> List.exactlyOne + // Attribute is on line 3 (1-based, after the leading empty line + `module M`). + Assert.Equal(3, attribDiag.Range.StartLine) + + [] + let ``Under preview langversion FS3888 is an error (feature ErrorOnMissingSignatureAttribute)`` () = + let sigSrc = """ +module M +val inline f: x: int -> int +""" + let implSrc = """ +module M +[] +let inline f (x: int) = x + 1 +""" + fsFromString (fsi sigSrc) + |> FS + |> withAdditionalSourceFile (fs implSrc) + |> withLangVersionPreview + |> asLibrary + |> compile + |> shouldFail + |> withErrorCode 3888 + |> withDiagnosticMessageMatches "NoDynamicInvocation" + + [] + let ``Under default langversion FS3888 is a warning (feature off)`` () = + let sigSrc = """ +module M +val inline f: x: int -> int +""" + let implSrc = """ +module M +[] +let inline f (x: int) = x + 1 +""" + fsFromString (fsi sigSrc) + |> FS + |> withAdditionalSourceFile (fs implSrc) + |> withLangVersion90 + |> asLibrary + |> ignoreWarnings + |> compile + |> shouldSucceed + |> withWarningCode 3888 + |> withDiagnosticMessageMatches "NoDynamicInvocation" + + // Internal-symbol scope: same .fsi/.fs divergence applies (cross-file + InternalsVisibleTo). + + [] + let ``Internal type with attribute mismatch still fires FS3888`` () = + let sigSrc = """ +module M +type internal C = { X: int } +""" + let implSrc = """ +module M +[] +type internal C = { X: int } +""" + compileSigImpl sigSrc implSrc + |> withDiagnosticMessageMatches "NoEquality" + + [] + let ``Internal val with attribute mismatch still fires FS3888`` () = + let sigSrc = """ +module M +val inline internal f: x: int -> int +""" + let implSrc = """ +module M +[] +let inline internal f (x: int) = x + 1 +""" + compileSigImpl sigSrc implSrc + |> shouldSucceed + |> withWarningCode 3888 + |> withDiagnosticMessageMatches "NoDynamicInvocation" + + // Expanded attribute set: typecheck-affecting attributes added after the initial PR. + + [] + let ``StructuralEquality/Comparison on impl but not sig is documentary and does NOT fire FS3888`` () = + // StructuralEquality / StructuralComparison on a record matches the F# default; + // the attributes are documentary and have no observable consumer effect. + let sigSrc = """ +module M +type R = { X: int } +""" + let implSrc = """ +module M +[] +type R = { X: int } +""" + compileSigImpl sigSrc implSrc + |> shouldSucceed + |> withWarningCodes [] + + [] + let ``IsReadOnly mismatch fires FS3888`` () = + let sigSrc = """ +module M +[] +type R = { X: int } +""" + let implSrc = """ +module M +[] +[] +type R = { X: int } +""" + compileSigImpl sigSrc implSrc + |> shouldSucceed + |> withWarningCode 3888 + |> withDiagnosticMessageMatches "IsReadOnly" + + [] + let ``Struct attribute mismatch fires FS3888`` () = + // Sig as class, impl as struct: boxing/byref semantics flip. + let sigSrc = """ +module M +type R = { X: int } +""" + let implSrc = """ +module M +[] +type R = { X: int } +""" + compileSigImpl sigSrc implSrc + |> withDiagnosticMessageMatches "Struct" + + [] + let ``RequireQualifiedAccess on nested module: impl-only fires FS3888`` () = + let sigSrc = """ +module M +module Inner = + val x: int +""" + let implSrc = """ +module M +[] +module Inner = + let x = 42 +""" + compileSigImpl sigSrc implSrc + |> shouldSucceed + |> withWarningCode 3888 + |> withDiagnosticMessageMatches "RequireQualifiedAccess" diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index 954c02b8aca..cbe8aedce55 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -211,6 +211,7 @@ + diff --git a/tests/fsharp/typecheck/sigs/neg31.bsl b/tests/fsharp/typecheck/sigs/neg31.bsl index 9140452d235..92b01723fd1 100644 --- a/tests/fsharp/typecheck/sigs/neg31.bsl +++ b/tests/fsharp/typecheck/sigs/neg31.bsl @@ -5,8 +5,12 @@ neg31.fs(71,12,71,70): typecheck error FS1200: The attribute 'ObsoleteAttribute' neg31.fs(107,13,107,48): typecheck error FS1200: The attribute 'CLSCompliantAttribute' appears in both the implementation and the signature, but the attribute arguments differ. Only the attribute from the signature will be included in the compiled code. +neg31.fs(32,6,32,82): typecheck error FS3888: The attribute 'Obsolete' is present on 'C3' 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. + neg31.fs(28,6,28,64): typecheck error FS1200: The attribute 'ObsoleteAttribute' appears in both the implementation and the signature, but the attribute arguments differ. Only the attribute from the signature will be included in the compiled code. neg31.fs(93,14,93,49): typecheck error FS1200: The attribute 'CLSCompliantAttribute' appears in both the implementation and the signature, but the attribute arguments differ. Only the attribute from the signature will be included in the compiled code. +neg31.fs(52,6,52,82): typecheck error FS3888: The attribute 'Obsolete' is present on 'M3' 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. + neg31.fs(47,6,47,64): typecheck error FS1200: The attribute 'ObsoleteAttribute' appears in both the implementation and the signature, but the attribute arguments differ. Only the attribute from the signature will be included in the compiled code. diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingAttributeToSignature.fs b/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingAttributeToSignature.fs new file mode 100644 index 00000000000..3269c23c67f --- /dev/null +++ b/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingAttributeToSignature.fs @@ -0,0 +1,242 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace Microsoft.VisualStudio.FSharp.Editor + +open System +open System.Composition +open System.Collections.Immutable + +open Microsoft.CodeAnalysis +open Microsoft.CodeAnalysis.CodeActions +open Microsoft.CodeAnalysis.CodeFixes +open Microsoft.CodeAnalysis.Text + +open FSharp.Compiler.Symbols + +open CancellableTasks + +/// Code-fix for FS3888: inserts the missing attribute into the .fsi above the +/// matching declaration. Cross-document fix (diagnostic in .fs, edit in .fsi). +[] +type internal AddMissingAttributeToSignatureCodeFixProvider [] () = + inherit CodeFixProvider() + + // Path normalized to handle slash/case/relative differences between FCS and Roslyn. + let tryFindSigDocument (document: Document) (sigFilePath: string) = + let solution = document.Project.Solution + + let normalizedPath = + try + System.IO.Path.GetFullPath(sigFilePath) + with _ -> + sigFilePath + + let docIds = solution.GetDocumentIdsWithFilePath(normalizedPath) + + if docIds.IsEmpty then + None + else + let preferred = docIds |> Seq.tryFind (fun id -> id.ProjectId = document.Project.Id) + + (preferred |> Option.defaultValue docIds.[0]) + |> solution.GetDocument + |> Option.ofObj + + // `Conditional("DEBUG")` -> `("Conditional", "(\"DEBUG\")")`. + let splitAttribHead (text: string) : struct (string * string) = + let mutable i = 0 + + while i < text.Length && text.[i] <> '(' && not (Char.IsWhiteSpace(text.[i])) do + i <- i + 1 + + struct (text.Substring(0, i), text.Substring(i)) + + // Negative-lookbehind guards against re-qualifying already-qualified or substring-of-longer-identifier tokens. + let qualifyEnumToken (simple: string) (qualified: string) (text: string) : string = + let pattern = $@"(? "System.Diagnostics." + head + | "EditorBrowsable" + | "EditorBrowsableAttribute" -> "System.ComponentModel." + head + | "NoEagerConstraintApplication" + | "NoEagerConstraintApplicationAttribute" -> "Microsoft.FSharp.Core.CompilerServices." + head + | "Obsolete" + | "ObsoleteAttribute" -> "System." + head + | "AttributeUsage" + | "AttributeUsageAttribute" -> "System." + head + | "Unverifiable" + | "UnverifiableAttribute" -> "Microsoft.FSharp.Core.CompilerServices." + head + | _ -> head + + let qualifiedRest = + rest + |> qualifyEnumToken "EditorBrowsableState" "System.ComponentModel.EditorBrowsableState" + |> qualifyEnumToken "AttributeTargets" "System.AttributeTargets" + + qualifiedHead + qualifiedRest + + let indentOfLine (sigSourceText: SourceText) (lineStart: int) = + let line = sigSourceText.Lines.GetLineFromPosition(lineStart).ToString() + let mutable i = 0 + + while i < line.Length && (line.[i] = ' ' || line.[i] = '\t') do + i <- i + 1 + + line.Substring(0, i) + + // Reuse the .fsi's existing newline convention; if the target line has no + // terminator, walk backward to find one before falling back to Environment.NewLine. + let lineBreakAt (sigSourceText: SourceText) (lineStart: int) = + let inline lineBreakOf (line: TextLine) = + let lbLen = line.EndIncludingLineBreak - line.End + + if lbLen > 0 then + Some(sigSourceText.ToString(TextSpan(line.End, lbLen))) + else + None + + let lines = sigSourceText.Lines + let startLineNo = lines.GetLineFromPosition(lineStart).LineNumber + let mutable result: string option = None + let mutable i = startLineNo + + while result.IsNone && i >= 0 do + result <- lineBreakOf lines.[i] + i <- i - 1 + + result |> Option.defaultValue Environment.NewLine + + // Bails out if the .fsi was truncated between registration and apply. + let tryFSharpRangeToTextSpan (text: SourceText) (range: FSharp.Compiler.Text.range) = + try + Some(RoslynHelpers.FSharpRangeToTextSpan(text, range)) + with + | :? ArgumentOutOfRangeException + | :? IndexOutOfRangeException -> None + + override _.FixableDiagnosticIds = ImmutableArray.Create "FS3888" + + override _.RegisterCodeFixesAsync context = + cancellableTask { + let document = context.Document + let! sourceText = document.GetTextAsync(context.CancellationToken) + + // SynAttribute.Range covers one attribute body without `[<` `>]` or sibling separators. + let attribSpan = context.Span + let rawAttribText = sourceText.GetSubText(attribSpan).ToString() + + if String.IsNullOrWhiteSpace rawAttribText then + () + else + + let attribText = canonicalizeAttribName rawAttribText + let bracketed = $"[<{attribText}>]" + + // Position-based lookup is unreliable in `[]` / `[]\n[]` + // (lands on sibling attribute or on `let`/`type`/`module`). Enumerate + // symbol uses and pick the first definition starting after the diagnostic + // whose SignatureLocation points into a different file (the .fsi). + let! _, checkResults = document.GetFSharpParseAndCheckResultsAsync "AddMissingAttributeToSignature" + + let diagFsRange = + RoslynHelpers.TextSpanToFSharpRange(document.FilePath, attribSpan, sourceText) + + let candidates = + checkResults.GetAllUsesOfAllSymbolsInFile(context.CancellationToken) + |> Seq.filter (fun (u: FSharp.Compiler.CodeAnalysis.FSharpSymbolUse) -> + u.IsFromDefinition + && u.Symbol.SignatureLocation.IsSome + // SignatureLocation must point into the .fsi, not back at the .fs. + // The wildcard self-identifier `_` in `member _.F = ...` is reported + // as a definition whose SignatureLocation is its own .fs position. + && (match u.Symbol.SignatureLocation with + | Some sigLoc -> not (String.Equals(sigLoc.FileName, document.FilePath, StringComparison.OrdinalIgnoreCase)) + | None -> false) + // Skip the implicit constructor of `type T() = ...`: its + // SignatureLocation points at `new: ...`, not at the member. + && (match u.Symbol with + | :? FSharpMemberOrFunctionOrValue as mfv -> not mfv.IsConstructor + | _ -> true) + && (u.Range.StartLine > diagFsRange.EndLine + || (u.Range.StartLine = diagFsRange.EndLine + && u.Range.StartColumn >= diagFsRange.EndColumn))) + |> Seq.toArray + + let symbolUse = + if candidates.Length = 0 then + None + else + // Tie-break by symbol name for determinism across overloads / type+ctor pairs. + candidates + |> Array.minBy (fun u -> + u.Range.StartLine, u.Range.StartColumn, u.Range.EndLine, u.Range.EndColumn, u.Symbol.FullName) + |> Some + + match symbolUse |> Option.bind (fun u -> u.Symbol.SignatureLocation) with + | Some sigRange when not (String.Equals(sigRange.FileName, document.FilePath, StringComparison.OrdinalIgnoreCase)) -> + match tryFindSigDocument document sigRange.FileName with + | Some sigDoc -> + // Keep the DocumentId, not the Document: re-resolve at apply + // time so intervening .fsi edits are observed. + let sigDocId = sigDoc.Id + + let normalizedSigPath = + try + System.IO.Path.GetFullPath(sigRange.FileName) + with _ -> + sigRange.FileName + + let createChangedSolution + (cancellationToken: System.Threading.CancellationToken) + : System.Threading.Tasks.Task = + task { + let currentSolution = document.Project.Solution + + match currentSolution.GetDocument(sigDocId) |> Option.ofObj with + | None -> return currentSolution + | Some liveSigDoc -> + let! current = liveSigDoc.GetTextAsync(cancellationToken) + + match tryFSharpRangeToTextSpan current sigRange with + | None -> return currentSolution + | Some currentSigSpan -> + let currentLineStart = current.Lines.GetLineFromPosition(currentSigSpan.Start).Start + let currentIndent = indentOfLine current currentLineStart + let currentLineBreak = lineBreakAt current currentLineStart + let currentInsertion = $"{currentIndent}{bracketed}{currentLineBreak}" + + let updated = + current.WithChanges(TextChange(TextSpan(currentLineStart, 0), currentInsertion)) + + return liveSigDoc.WithText(updated).Project.Solution + } + + let action = + CodeAction.Create( + $"Add {bracketed} to signature", + System.Func>( + createChangedSolution + ), + equivalenceKey = + $"{CodeFix.AddMissingAttributeToSignature}:{bracketed}:{normalizedSigPath}:{sigRange.StartLine}:{sigRange.StartColumn}:{sigRange.EndLine}:{sigRange.EndColumn}" + ) + + context.RegisterCodeFix(action, context.Diagnostics) + | None -> () + | Some _ + | None -> () + } + |> CancellableTask.startAsTask context.CancellationToken diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/RemoveExtraAttributeFromImplementation.fs b/vsintegration/src/FSharp.Editor/CodeFixes/RemoveExtraAttributeFromImplementation.fs new file mode 100644 index 00000000000..baae7e7c657 --- /dev/null +++ b/vsintegration/src/FSharp.Editor/CodeFixes/RemoveExtraAttributeFromImplementation.fs @@ -0,0 +1,126 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace Microsoft.VisualStudio.FSharp.Editor + +open System +open System.Composition +open System.Collections.Immutable + +open Microsoft.CodeAnalysis +open Microsoft.CodeAnalysis.CodeActions +open Microsoft.CodeAnalysis.CodeFixes +open Microsoft.CodeAnalysis.Text + +open CancellableTasks + +/// Reverse code-fix for FS3888: removes the offending attribute from the .fs. +[] +type internal RemoveExtraAttributeFromImplementationCodeFixProvider [] () = + inherit CodeFixProvider() + + // Expand the SynAttribute.Range body span to the smallest enclosing chunk we can delete: + // []\n -> bracket + trailing newline + indent + // [] drop A -> "A; " + // [] drop B -> "; B" + // Returns None on unrecognized layouts (e.g. multi-line `[<\nA\n>]`). + let computeDeletionSpan (text: SourceText) (attribSpan: TextSpan) : TextSpan option = + let s = text.ToString() + + let nextNonWhitespaceForward pos = + let mutable i = pos + + while i < s.Length && (s.[i] = ' ' || s.[i] = '\t') do + i <- i + 1 + + i + + let nextNonWhitespaceBackward pos = + let mutable i = pos + + while i > 0 && (s.[i - 1] = ' ' || s.[i - 1] = '\t') do + i <- i - 1 + + i + + let leftTrim = nextNonWhitespaceBackward attribSpan.Start + let rightTrim = nextNonWhitespaceForward attribSpan.End + + let hasLeftSemi = leftTrim > 0 && s.[leftTrim - 1] = ';' + let hasRightSemi = rightTrim < s.Length && s.[rightTrim] = ';' + + let lookingAtBracketOpen pos = + pos >= 2 && s.[pos - 2] = '[' && s.[pos - 1] = '<' + + let lookingAtBracketClose pos = + pos + 1 < s.Length && s.[pos] = '>' && s.[pos + 1] = ']' + + if hasLeftSemi then + Some(TextSpan.FromBounds(leftTrim - 1, attribSpan.End)) + elif hasRightSemi then + let mutable rs = rightTrim + 1 + + while rs < s.Length && (s.[rs] = ' ' || s.[rs] = '\t') do + rs <- rs + 1 + + Some(TextSpan.FromBounds(attribSpan.Start, rs)) + elif lookingAtBracketOpen leftTrim && lookingAtBracketClose rightTrim then + let mutable deletionStart = leftTrim - 2 + let mutable deletionEnd = rightTrim + 2 + + if deletionEnd < s.Length && s.[deletionEnd] = '\r' then + deletionEnd <- deletionEnd + 1 + + if deletionEnd < s.Length && s.[deletionEnd] = '\n' then + deletionEnd <- deletionEnd + 1 + + // Only absorb indentation if the bracket is on its own line, otherwise it could be inline with other code. + let mutable indentStart = deletionStart + + while indentStart > 0 && (s.[indentStart - 1] = ' ' || s.[indentStart - 1] = '\t') do + indentStart <- indentStart - 1 + + if indentStart = 0 || s.[indentStart - 1] = '\n' || s.[indentStart - 1] = '\r' then + deletionStart <- indentStart + + Some(TextSpan.FromBounds(deletionStart, deletionEnd)) + else + None + + override _.FixableDiagnosticIds = ImmutableArray.Create "FS3888" + + override _.RegisterCodeFixesAsync context = + cancellableTask { + let document = context.Document + let! sourceText = document.GetTextAsync(context.CancellationToken) + + let attribSpan = context.Span + + if attribSpan.IsEmpty then + () + else + let attribText = sourceText.GetSubText(attribSpan).ToString() + + if String.IsNullOrWhiteSpace attribText then + () + else + match computeDeletionSpan sourceText attribSpan with + | None -> () + | Some deletion -> + let title = $"Remove [<{attribText}>] from implementation" + + let action = + CodeAction.Create( + title, + System.Func>(fun ct -> + task { + let! current = document.GetTextAsync(ct) + let updated = current.WithChanges(TextChange(deletion, "")) + return document.WithText(updated) + }), + equivalenceKey = + $"{CodeFix.RemoveExtraAttributeFromImplementation}:{attribText}:{deletion.Start}:{deletion.End}" + ) + + context.RegisterCodeFix(action, context.Diagnostics) + } + |> CancellableTask.startAsTask context.CancellationToken diff --git a/vsintegration/src/FSharp.Editor/Common/Constants.fs b/vsintegration/src/FSharp.Editor/Common/Constants.fs index 24ee22eb433..ead451467cf 100644 --- a/vsintegration/src/FSharp.Editor/Common/Constants.fs +++ b/vsintegration/src/FSharp.Editor/Common/Constants.fs @@ -101,6 +101,13 @@ module internal CodeFix = [] let AddParentheses = "AddParentheses" + [] + let AddMissingAttributeToSignature = "AddMissingAttributeToSignature" + + [] + let RemoveExtraAttributeFromImplementation = + "RemoveExtraAttributeFromImplementation" + [] let AddTypeAnnotationToObjectOfIndeterminateType = "AddTypeAnnotationToObjectOfIndeterminateType" diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj index ea8a0f15921..68206f698bd 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj @@ -121,6 +121,8 @@ + + diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/AddMissingAttributeToSignatureTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/AddMissingAttributeToSignatureTests.fs new file mode 100644 index 00000000000..91cd072de59 --- /dev/null +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/AddMissingAttributeToSignatureTests.fs @@ -0,0 +1,631 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +module FSharp.Editor.Tests.CodeFixes.AddMissingAttributeToSignatureTests + +open System.Collections.Immutable +open System.Threading + +open Microsoft.CodeAnalysis +open Microsoft.CodeAnalysis.CodeActions +open Microsoft.CodeAnalysis.CodeFixes +open Microsoft.CodeAnalysis.Text + +open Microsoft.VisualStudio.FSharp.Editor + +open FSharp.Editor.Tests.Helpers +open FSharp.Editor.Tests.CodeFixes.CodeFixTestFramework +open Xunit + +let private codeFix = AddMissingAttributeToSignatureCodeFixProvider() + +/// Cross-document harness: builds an .fsi + .fs pair, runs the F# checker, +/// finds the FS3888 diagnostic at index `diagIndex` on the .fs, invokes the +/// code-fix, captures the registered CodeAction, applies it and returns the +/// resulting .fsi text. Use `tryFixSig` for the common case (first diagnostic). +let private tryFixSigAt (diagIndex: int) (fsiCode: string) (fsCode: string) : string option = + let documents = RoslynTestHelpers.GetFsiAndFsDocuments fsiCode fsCode |> Seq.toList + let fsiDoc = documents |> List.find (fun d -> d.IsFSharpSignatureFile) + let fsDoc = documents |> List.find (fun d -> not d.IsFSharpSignatureFile) + + let sourceText = fsDoc.GetTextAsync().Result + + let _, checkResults = + fsDoc.GetFSharpParseAndCheckResultsAsync "test" + |> Microsoft.VisualStudio.FSharp.Editor.CancellableTasks.CancellableTask.runSynchronouslyWithoutCancellation + + let diagnostics = + checkResults.Diagnostics + |> Array.filter (fun d -> d.ErrorNumber = 3888) + |> Array.map (Diagnostic.ofFSharpDiagnostic sourceText fsDoc.FilePath) + + if diagIndex >= diagnostics.Length then + None + else + let diagnostic = diagnostics[diagIndex] + let mutable captured: CodeAction option = None + + let register = + System.Action>(fun action _ -> captured <- Some action) + + let ctx = + CodeFixContext(fsDoc, diagnostic.Location.SourceSpan, ImmutableArray.Create diagnostic, register, CancellationToken.None) + + codeFix.RegisterCodeFixesAsync(ctx).Wait() + + match captured with + | None -> None + | Some action -> + let operations = action.GetOperationsAsync(CancellationToken.None).Result + + let applyOp = + operations + |> Seq.pick (function + | :? ApplyChangesOperation as op -> Some op + | _ -> None) + + let newSolution = applyOp.ChangedSolution + let newFsi = newSolution.GetDocument(fsiDoc.Id) + Some((newFsi.GetTextAsync().Result).ToString()) + +let private tryFixSig fsiCode fsCode = tryFixSigAt 0 fsiCode fsCode + +let private countDiags (fsiCode: string) (fsCode: string) : int = + let documents = RoslynTestHelpers.GetFsiAndFsDocuments fsiCode fsCode |> Seq.toList + let fsDoc = documents |> List.find (fun d -> not d.IsFSharpSignatureFile) + + let _, checkResults = + fsDoc.GetFSharpParseAndCheckResultsAsync "test" + |> Microsoft.VisualStudio.FSharp.Editor.CancellableTasks.CancellableTask.runSynchronouslyWithoutCancellation + + checkResults.Diagnostics + |> Array.filter (fun d -> d.ErrorNumber = 3888) + |> Array.length + +[] +let ``Module-level: RequireQualifiedAccess on nested module is inserted into .fsi`` () = + let fsiCode = + """ +module M +module Inner = + val x: int +""" + + let fsCode = + """ +module M +[] +module Inner = + let x = 42 +""" + + let expectedFsi = + """ +module M +[] +module Inner = + val x: int +""" + + let actual = tryFixSig fsiCode fsCode + Assert.Equal(expectedFsi, actual |> Option.defaultValue "") + +[] +let ``Type-level: RequireQualifiedAccess on union is inserted into .fsi`` () = + let fsiCode = + """ +module M +type U = A | B +""" + + let fsCode = + """ +module M +[] +type U = A | B +""" + + let expectedFsi = + """ +module M +[] +type U = A | B +""" + + let actual = tryFixSig fsiCode fsCode + Assert.Equal(expectedFsi, actual |> Option.defaultValue "") + +[] +let ``Function-level: NoDynamicInvocation on val is inserted into .fsi`` () = + let fsiCode = + """ +module M +val inline f: x: int -> int +""" + + let fsCode = + """ +module M +[] +let inline f (x: int) = x + 1 +""" + + let expectedFsi = + """ +module M +[] +val inline f: x: int -> int +""" + + let actual = tryFixSig fsiCode fsCode + Assert.Equal(expectedFsi, actual |> Option.defaultValue "") + +[] +let ``Attribute with argument: AllowNullLiteral(false) is copied verbatim with args`` () = + let fsiCode = + """ +module M +type C = + new: unit -> C +""" + + let fsCode = + """ +module M +[] +type C() = class end +""" + + let expectedFsi = + """ +module M +[] +type C = + new: unit -> C +""" + + let actual = tryFixSig fsiCode fsCode + Assert.Equal(expectedFsi, actual |> Option.defaultValue "") + +// ------------------------------------------------------------------------- +// Multi-attribute scenarios +// ------------------------------------------------------------------------- + +[] +let ``Two enforced attributes stacked on separate lines produce two independent fixes`` () = + let fsiCode = + """ +module M +val inline f: x: int -> int +""" + + let fsCode = + """ +module M +[] +[] +let inline f (x: int) = x + 1 +""" + // Two FS3888 diagnostics fire (one per missing attribute). + Assert.Equal(2, countDiags fsiCode fsCode) + + // First fix: NoDynamicInvocation. + let firstFsi = tryFixSigAt 0 fsiCode fsCode + Assert.Contains("[]", firstFsi |> Option.defaultValue "") + + // Second fix: RequiresExplicitTypeArguments. + let secondFsi = tryFixSigAt 1 fsiCode fsCode + Assert.Contains("[]", secondFsi |> Option.defaultValue "") + +[] +let ``Two enforced attributes on one line [] produce two independent fixes`` () = + let fsiCode = + """ +module M +val inline f: x: int -> int +""" + + let fsCode = + """ +module M +[] +let inline f (x: int) = x + 1 +""" + + Assert.Equal(2, countDiags fsiCode fsCode) + + let firstFsi = tryFixSigAt 0 fsiCode fsCode |> Option.defaultValue "" + // First diagnostic (NoDynamicInvocation): inserted as its OWN [< >] block + // - not concatenated with the second attribute. The wrap is per-attribute + // because SynAttribute.Range covers one attribute body, not the whole list. + Assert.Contains("[]", firstFsi) + // Should not have leaked the second attribute's text into the insertion. + Assert.DoesNotContain("[ Option.defaultValue "" + Assert.Contains("[]", secondFsi) + Assert.DoesNotContain("[] +let ``Mixed: enforced attr next to a non-enforced attr on same line - only the enforced one is copied`` () = + let fsiCode = + """ +module M +val inline f: x: int -> int +""" + + let fsCode = + """ +module M +[] +let inline f (x: int) = x + 1 +""" + + Assert.Equal(1, countDiags fsiCode fsCode) + let fsi = tryFixSig fsiCode fsCode |> Option.defaultValue "" + Assert.Contains("[]", fsi) + // System.Obsolete is not enforced - must not appear in the .fsi insertion. + Assert.DoesNotContain("System.Obsolete", fsi) + +[] +let ``Non-enforced attribute already on .fsi declaration - new attribute is added above and the existing one is kept`` () = + let fsiCode = + """ +module M +[] +val inline f: x: int -> int +""" + + let fsCode = + """ +module M +[] +[] +let inline f (x: int) = x + 1 +""" + + let fsi = tryFixSig fsiCode fsCode |> Option.defaultValue "" + // Both attributes should be present on the val in the .fsi. + Assert.Contains("[]", fsi) + Assert.Contains("""[]""", fsi) + +// ------------------------------------------------------------------------- +// Strengthened multi-attribute: exact text instead of Contains +// ------------------------------------------------------------------------- + +[] +let ``Stacked [][] both missing: first fix yields exact expected .fsi`` () = + let fsiCode = + """ +module M +val inline f: x: int -> int +""" + + let fsCode = + """ +module M +[] +[] +let inline f (x: int) = x + 1 +""" + + let expected = + """ +module M +[] +val inline f: x: int -> int +""" + + let actual = tryFixSigAt 0 fsiCode fsCode + Assert.Equal(expected, actual |> Option.defaultValue "") + +[] +let ``Semicolon [] both missing: first fix yields exact expected .fsi`` () = + let fsiCode = + """ +module M +val inline f: x: int -> int +""" + + let fsCode = + """ +module M +[] +let inline f (x: int) = x + 1 +""" + + let expected = + """ +module M +[] +val inline f: x: int -> int +""" + + let actual = tryFixSigAt 0 fsiCode fsCode + Assert.Equal(expected, actual |> Option.defaultValue "") + +// ------------------------------------------------------------------------- +// Symbol-lookup edge cases (top-level module, member-in-type) +// ------------------------------------------------------------------------- + +[] +let ``Top-level module attribute is inserted on the .fsi module line`` () = + let fsiCode = + """ +module M.Sub +val x: int +""" + + let fsCode = + """ +[] +module M.Sub +let x = 1 +""" + + let expected = + """ +[] +module M.Sub +val x: int +""" + + let actual = tryFixSig fsiCode fsCode + Assert.Equal(expected, actual |> Option.defaultValue "") + +[] +let ``Member inside type with NoDynamicInvocation: fix targets the member sig line`` () = + let fsiCode = + """ +module M +type T = + new: unit -> T + member inline F: x: int -> int +""" + + let fsCode = + """ +module M +type T() = + [] + member inline _.F(x: int) = x + 1 +""" + + let fsi = tryFixSig fsiCode fsCode |> Option.defaultValue "" + // The inserted attribute should be on the line directly above + // `member inline F:` in the .fsi (not above `new:` and not above `type T =`). + let lines = fsi.Split([| '\n' |]) + + let memberLineIdx = + lines + |> Array.findIndex (fun line -> line.TrimStart().StartsWith("member inline F:")) + + let prevLine = lines.[memberLineIdx - 1].TrimEnd() + Assert.Equal(" []", prevLine) + +// ------------------------------------------------------------------------- +// CodeAction title contract +// ------------------------------------------------------------------------- + +[] +let ``CodeAction title includes the bracketed attribute text`` () = + let fsiCode = + """ +module M +val inline f: x: int -> int +""" + + let fsCode = + """ +module M +[] +let inline f (x: int) = x + 1 +""" + + let documents = RoslynTestHelpers.GetFsiAndFsDocuments fsiCode fsCode |> Seq.toList + let fsDoc = documents |> List.find (fun d -> not d.IsFSharpSignatureFile) + let sourceText = fsDoc.GetTextAsync().Result + + let _, checkResults = + fsDoc.GetFSharpParseAndCheckResultsAsync "test" + |> Microsoft.VisualStudio.FSharp.Editor.CancellableTasks.CancellableTask.runSynchronouslyWithoutCancellation + + let diagnostic = + checkResults.Diagnostics + |> Array.find (fun d -> d.ErrorNumber = 3888) + |> Diagnostic.ofFSharpDiagnostic sourceText fsDoc.FilePath + + let mutable captured: CodeAction option = None + + let register = + System.Action>(fun a _ -> captured <- Some a) + + let ctx = + CodeFixContext(fsDoc, diagnostic.Location.SourceSpan, ImmutableArray.Create diagnostic, register, CancellationToken.None) + + codeFix.RegisterCodeFixesAsync(ctx).Wait() + + let action = + captured + |> Option.defaultWith (fun () -> failwith "expected a code-fix to be registered") + + Assert.Equal("Add [] to signature", action.Title) + +// ------------------------------------------------------------------------- +// Canonicalization: attributes whose .fs form needs an `open` get qualified +// so the inserted .fsi compiles without extra opens +// ------------------------------------------------------------------------- + +[] +let ``Conditional in .fs gets qualified as System.Diagnostics.Conditional in .fsi`` () = + let fsiCode = + """ +module M +type T = + new: unit -> T + member F: x: int -> unit +""" + // .fs has `open System.Diagnostics`; .fsi does NOT - the inserted + // attribute must qualify, otherwise the .fsi fails to compile. + let fsCode = + """ +module M +open System.Diagnostics +type T() = + [] + member _.F(x: int) = () +""" + + let fsi = tryFixSig fsiCode fsCode |> Option.defaultValue "" + Assert.Contains("[]", fsi) + // The bare Conditional form should NOT be present. + Assert.DoesNotContain("[]", fsi) + +[] +let ``EditorBrowsable in .fs gets qualified as System.ComponentModel.EditorBrowsable in .fsi`` () = + let fsiCode = + """ +module M +type T = class end +""" + + let fsCode = + """ +module M +open System.ComponentModel +[] +type T() = class end +""" + + let fsi = tryFixSig fsiCode fsCode |> Option.defaultValue "" + // Both the attribute head AND its enum-typed argument must be qualified + // so the .fsi compiles without `open System.ComponentModel`. + Assert.Contains("[]", fsi) + +[] +let ``Conditional with a dotted string argument is still canonicalized`` () = + // Regression: an earlier `attribText.Contains(".")` check would skip + // canonicalization for `Conditional("DEBUG.V1")` because of the `.` in + // the argument. Now only the attribute HEAD is checked for qualification. + let fsiCode = + """ +module M +type T = + new: unit -> T + member F: x: int -> unit +""" + + let fsCode = + """ +module M +open System.Diagnostics +type T() = + [] + member _.F(x: int) = () +""" + + let fsi = tryFixSig fsiCode fsCode |> Option.defaultValue "" + Assert.Contains("[]", fsi) + +[] +let ``Already-qualified attribute name is left alone (no double-qualify)`` () = + let fsiCode = + """ +module M +val inline f: x: int -> int +""" + + let fsCode = + """ +module M +[] +let inline f (x: int) = x + 1 +""" + + let fsi = tryFixSig fsiCode fsCode |> Option.defaultValue "" + Assert.Contains("[]", fsi) + +[] +let ``Obsolete in .fs gets qualified as System.Obsolete in .fsi`` () = + let fsiCode = + """ +module M +type T = class end +""" + + let fsCode = + """ +module M +open System +[] +type T() = class end +""" + + let fsi = tryFixSig fsiCode fsCode |> Option.defaultValue "" + Assert.Contains("[]", fsi) + Assert.DoesNotContain("[]", fsi) + +[] +let ``AttributeUsage with AttributeTargets enum gets head AND enum qualified`` () = + let fsiCode = + """ +module M +type MyAttr = + inherit System.Attribute + new: unit -> MyAttr +""" + + let fsCode = + """ +module M +open System +[] +type MyAttr() = + inherit Attribute() +""" + + let fsi = tryFixSig fsiCode fsCode |> Option.defaultValue "" + Assert.Contains("[]", fsi) + +[] +let ``Qualified EditorBrowsable head with bare enum arg still qualifies the enum`` () = + // Regression: previously the head check `head.Contains(".")` short- + // circuited the WHOLE canonicalization, leaving the bare enum reference + // in place. Now the enum-arg rewrite runs independently of head qualification. + let fsiCode = + """ +module M +type T = class end +""" + + let fsCode = + """ +module M +open System.ComponentModel +[] +type T() = class end +""" + + let fsi = tryFixSig fsiCode fsCode |> Option.defaultValue "" + Assert.Contains("[]", fsi) + +[] +let ``Already-qualified enum reference is not double-qualified`` () = + // Regression: a naive `Replace` would turn + // `System.ComponentModel.EditorBrowsableState.Never` into + // `System.ComponentModel.System.ComponentModel.EditorBrowsableState.Never`. + let fsiCode = + """ +module M +type T = class end +""" + + let fsCode = + """ +module M +[] +type T() = class end +""" + + let fsi = tryFixSig fsiCode fsCode |> Option.defaultValue "" + Assert.Contains("[]", fsi) + Assert.DoesNotContain("System.ComponentModel.System.ComponentModel.", fsi) diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveExtraAttributeFromImplementationTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveExtraAttributeFromImplementationTests.fs new file mode 100644 index 00000000000..b90aee5de4a --- /dev/null +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveExtraAttributeFromImplementationTests.fs @@ -0,0 +1,174 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +module FSharp.Editor.Tests.CodeFixes.RemoveExtraAttributeFromImplementationTests + +open System.Collections.Immutable +open System.Threading + +open Microsoft.CodeAnalysis +open Microsoft.CodeAnalysis.CodeActions +open Microsoft.CodeAnalysis.CodeFixes +open Microsoft.CodeAnalysis.Text + +open Microsoft.VisualStudio.FSharp.Editor + +open FSharp.Editor.Tests.Helpers +open FSharp.Editor.Tests.CodeFixes.CodeFixTestFramework +open Xunit + +let private codeFix = RemoveExtraAttributeFromImplementationCodeFixProvider() + +/// Same-document harness: builds an .fsi + .fs pair, runs the F# checker, +/// finds the FS3888 diagnostic at index `diagIndex` on the .fs, invokes the +/// reverse code-fix, and returns the resulting .fs text. +let private tryFixFsAt (diagIndex: int) (fsiCode: string) (fsCode: string) : string option = + let documents = RoslynTestHelpers.GetFsiAndFsDocuments fsiCode fsCode |> Seq.toList + let fsDoc = documents |> List.find (fun d -> not d.IsFSharpSignatureFile) + + let sourceText = fsDoc.GetTextAsync().Result + + let _, checkResults = + fsDoc.GetFSharpParseAndCheckResultsAsync "test" + |> Microsoft.VisualStudio.FSharp.Editor.CancellableTasks.CancellableTask.runSynchronouslyWithoutCancellation + + let diagnostics = + checkResults.Diagnostics + |> Array.filter (fun d -> d.ErrorNumber = 3888) + |> Array.map (Diagnostic.ofFSharpDiagnostic sourceText fsDoc.FilePath) + + if diagIndex >= diagnostics.Length then + None + else + let diagnostic = diagnostics[diagIndex] + let mutable captured: CodeAction option = None + + let register = + System.Action>(fun action _ -> captured <- Some action) + + let ctx = + CodeFixContext(fsDoc, diagnostic.Location.SourceSpan, ImmutableArray.Create diagnostic, register, CancellationToken.None) + + codeFix.RegisterCodeFixesAsync(ctx).Wait() + + match captured with + | None -> None + | Some action -> + let operations = action.GetOperationsAsync(CancellationToken.None).Result + + let applyOp = + operations + |> Seq.pick (function + | :? ApplyChangesOperation as op -> Some op + | _ -> None) + + let newSolution = applyOp.ChangedSolution + let newFsDoc = newSolution.GetDocument(fsDoc.Id) + Some((newFsDoc.GetTextAsync().Result).ToString()) + +let private tryFixFs fsiCode fsCode = tryFixFsAt 0 fsiCode fsCode + +[] +let ``Lone attribute on its own line is removed cleanly`` () = + let fsi = + """module M +val inline f: x: int -> int +""" + + let fs = + """module M +[] +let inline f (x: int) = x + 1 +""" + + let expected = + """module M +let inline f (x: int) = x + 1 +""" + + Assert.Equal(Some expected, tryFixFs fsi fs) + +[] +let ``First sibling in [] list is removed, second sibling preserved`` () = + let fsi = + """module M +val inline f: x: int -> int +""" + + let fs = + """module M +[] +let inline f (x: int) = x + 1 +""" + // Reverse-fix the FIRST diagnostic (NoDynamicInvocation). The other + // FS3888 (RequiresExplicitTypeArguments) is still pending but its + // separate code-action would remove the second sibling. + let expected = + """module M +[] +let inline f (x: int) = x + 1 +""" + + Assert.Equal(Some expected, tryFixFsAt 0 fsi fs) + +[] +let ``Second sibling in [] list is removed, first sibling preserved`` () = + let fsi = + """module M +val inline f: x: int -> int +""" + + let fs = + """module M +[] +let inline f (x: int) = x + 1 +""" + + let expected = + """module M +[] +let inline f (x: int) = x + 1 +""" + + Assert.Equal(Some expected, tryFixFsAt 1 fsi fs) + +[] +let ``Attribute on type with body is removed without breaking the type`` () = + let fsi = + """module M +type C = + new: unit -> C +""" + + let fs = + """module M +[] +type C() = class end +""" + + let expected = + """module M +type C() = class end +""" + + Assert.Equal(Some expected, tryFixFs fsi fs) + +[] +let ``Attribute with arguments is removed cleanly`` () = + let fsi = + """module M +type C = + new: unit -> C +""" + + let fs = + """module M +[] +type C() = class end +""" + + let expected = + """module M +type C() = class end +""" + + Assert.Equal(Some expected, tryFixFs fsi fs) diff --git a/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj b/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj index ff40debc105..0d05a915760 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj +++ b/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj @@ -47,6 +47,8 @@ + +