diff --git a/docs/release-notes/FSharp.Compiler.Service/8.0.200.md b/docs/release-notes/FSharp.Compiler.Service/8.0.200.md index c35c157fefb..53c6e8a4e67 100644 --- a/docs/release-notes/FSharp.Compiler.Service/8.0.200.md +++ b/docs/release-notes/FSharp.Compiler.Service/8.0.200.md @@ -2,3 +2,4 @@ - Parens: Keep parentheses around non-struct tuples in `&` patterns - https://github.com/dotnet/fsharp/pull/16391 - Parens analysis: fix some parenthesization corner-cases in record expressions - https://github.com/dotnet/fsharp/pull/16370 - Fixes #16359 - correctly handle imports with 0 length public key tokens - https://github.com/dotnet/fsharp/pull/16363 +- Raise a new error when interfaces with auto properties are implemented on constructor-less types - https://github.com/dotnet/fsharp/pull/16352 \ No newline at end of file diff --git a/src/Compiler/Checking/CheckDeclarations.fs b/src/Compiler/Checking/CheckDeclarations.fs index a6379e74893..8bc0da90686 100644 --- a/src/Compiler/Checking/CheckDeclarations.fs +++ b/src/Compiler/Checking/CheckDeclarations.fs @@ -4223,12 +4223,23 @@ module TcDeclarations = | _ -> () | ds -> // Check for duplicated parameters in abstract methods + // Check for an interface implementation with auto properties on constructor-less types for slot in ds do if isAbstractSlot slot then match slot with | SynMemberDefn.AbstractSlot (slotSig = synVal; range = m) -> CheckDuplicatesArgNames synVal m | _ -> () + + if isInterface slot then + match slot with + | SynMemberDefn.Interface (members= Some defs) -> + for au in defs do + match au with + | SynMemberDefn.AutoProperty(isStatic = false; range = m) -> + errorR(Error(FSComp.SR.tcAutoPropertyRequiresImplicitConstructionSequence(), m)) + | _ -> () + | _ -> () // Classic class construction let _, ds = List.takeUntil (allFalse [isMember;isAbstractSlot;isInterface;isInherit;isField;isTycon]) ds @@ -4253,9 +4264,6 @@ module TcDeclarations = | SynMemberDefn.LetBindings (range=m) :: _ -> errorR(Error(FSComp.SR.tcTypeDefinitionsWithImplicitConstructionMustHaveLocalBindingsBeforeMembers(), m)) | _ -> () - - - /// Split auto-properties into 'let' and 'member' bindings let private SplitAutoProps members = let membersIncludingAutoProps, vals_Inherits_Abstractslots = diff --git a/tests/FSharp.Compiler.ComponentTests/Diagnostics/Records.fs b/tests/FSharp.Compiler.ComponentTests/Diagnostics/Records.fs index ff741d40ea6..d617daaee7c 100644 --- a/tests/FSharp.Compiler.ComponentTests/Diagnostics/Records.fs +++ b/tests/FSharp.Compiler.ComponentTests/Diagnostics/Records.fs @@ -92,4 +92,23 @@ let t3 (x: RecTy) (a: AnotherNestedRecTy) = { x with D.C.c = { a with A = 3; B = |> withDiagnostics [ (Warning 3560, Line 9, Col 26, Line 9, Col 65, "This copy-and-update record expression changes all fields of record type 'Test.NestdRecTy'. Consider using the record construction syntax instead.") (Warning 3560, Line 15, Col 62, Line 15, Col 85, "This copy-and-update record expression changes all fields of record type 'Test.AnotherNestedRecTy'. Consider using the record construction syntax instead.") - ] \ No newline at end of file + ] + +[] +let ``Error when implementing interface with auto property in record type``() = + FSharp """ +type Foo = + abstract member X : string with get, set + abstract GetValue: unit -> string + +type FooImpl = + { name: string } + interface Foo with + member val X = "" with get, set + member x.GetValue() = x.name + """ + |> withLangVersion80 + |> asExe + |> compile + |> shouldFail + |> withSingleDiagnostic (Error 912, Line 9, Col 5, Line 9, Col 36, "This declaration element is not permitted in an augmentation") \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/ErrorMessages/ClassesTests.fs b/tests/FSharp.Compiler.ComponentTests/ErrorMessages/ClassesTests.fs index 41a672132be..5d8273748b9 100644 --- a/tests/FSharp.Compiler.ComponentTests/ErrorMessages/ClassesTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/ErrorMessages/ClassesTests.fs @@ -704,4 +704,82 @@ type X = abstract member Bar<'T> : string -> unit """ |> typecheck + |> shouldSucceed + + [] + let ``Error if we try to have auto properties on constructor-less types`` () = + Fsx """ +type Foo = + abstract member X : string with get, set + abstract member Y : string with get, set + abstract member Z : string with get, set + +type FooImpl = + interface Foo with + member val X = "" with get, set + member val Y = "" with get, set + member this.Z + with get() = "" + and set(value) = () + """ + |> asExe + |> compile + |> shouldFail + |> withDiagnostics [ + (Error 3133, Line 9, Col 9, Line 9, Col 40, "'member val' definitions are only permitted in types with a primary constructor. Consider adding arguments to your type definition, e.g. 'type X(args) = ...'."); + (Error 3133, Line 10, Col 9, Line 10, Col 40, "'member val' definitions are only permitted in types with a primary constructor. Consider adding arguments to your type definition, e.g. 'type X(args) = ...'.") + ] + + [] + let ``No error if we try to have auto properties on types with primary constructor`` () = + Fsx """ +type Foo = + abstract member X : string with get, set + abstract member Y : string with get, set + abstract member Z : string with get, set + +type FooImpl() = + interface Foo with + member val X = "" with get, set + member val Y = "" with get, set + member this.Z + with get() = "" + and set(value) = () + """ + |> typecheck + |> shouldSucceed + + [] + let ``No error if we try to have auto properties on types with primary constructor with args`` () = + Fsx """ +type Foo = + abstract member X : string with get, set + abstract member Y : string with get, set + abstract member Z : string with get, set + +type FooImpl(x) = + interface Foo with + member val X = "" with get, set + member val Y = "" with get, set + member this.Z + with get() = "" + and set(value) = () + """ + |> typecheck + |> shouldSucceed + + [] + let ``No error if we try to have static autoprop on a type without constructor`` () = + Fsx """ +#nowarn "3535" //We accept that static abstracts are an advanced feature +[] +type Foo = + static abstract member X : string with get, set + +[] +type FooImpl = + interface Foo with + static member val X = "" with get, set + """ + |> typecheck |> shouldSucceed \ No newline at end of file