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 f77a4a7613f..09463d94484 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/8.0.200.md +++ b/docs/release-notes/.FSharp.Compiler.Service/8.0.200.md @@ -2,6 +2,7 @@ * Miscellaneous fixes to parentheses analysis. ([PR #16262](https://github.com/dotnet/fsharp/pull/16262), [PR #16391](https://github.com/dotnet/fsharp/pull/16391), [PR #16370](https://github.com/dotnet/fsharp/pull/16370), [PR #16395](https://github.com/dotnet/fsharp/pull/16395)) * Correctly handle assembly imports with public key token of 0 length. ([Issue #16359](https://github.com/dotnet/fsharp/issues/16359), [PR #16363](https://github.com/dotnet/fsharp/pull/16363)) +* Fix #16398 - The dotnet framework has a limit of ~64K methods in a single class. Introduce a compile-time error if any class has over approx 64K methods in generated IL ### Added * Raise a new error when interfaces with auto properties are implemented on constructor-less types. ([PR #16352](https://github.com/dotnet/fsharp/pull/16352)) diff --git a/src/Compiler/AbstractIL/ilwrite.fs b/src/Compiler/AbstractIL/ilwrite.fs index 5c007513153..f41c8aaa08d 100644 --- a/src/Compiler/AbstractIL/ilwrite.fs +++ b/src/Compiler/AbstractIL/ilwrite.fs @@ -50,6 +50,13 @@ let emitBytesViaBuffer f = use bb = ByteBuffer.Create EmitBytesViaBufferCapacity /// Alignment and padding let align alignment n = ((n + alignment - 1) / alignment) * alignment + +/// Maximum number of methods in a dotnet type +/// This differs from the spec and file formats slightly which suggests 0xfffe is the maximum +/// this value was identified empirically. +[] +let maximumMethodsPerDotNetType = 0xfff0 + //--------------------------------------------------------------------- // Concrete token representations etc. used in PE files //--------------------------------------------------------------------- @@ -672,8 +679,14 @@ let GetTypeNameAsElemPair cenv n = //===================================================================== let rec GenTypeDefPass1 enc cenv (tdef: ILTypeDef) = - ignore (cenv.typeDefs.AddUniqueEntry "type index" (fun (TdKey (_, n)) -> n) (TdKey (enc, tdef.Name))) - GenTypeDefsPass1 (enc@[tdef.Name]) cenv (tdef.NestedTypes.AsList()) + ignore (cenv.typeDefs.AddUniqueEntry "type index" (fun (TdKey (_, n)) -> n) (TdKey (enc, tdef.Name))) + + // Verify that the typedef contains fewer than maximumMethodsPerDotNetType + let count = tdef.Methods.AsArray().Length + if count > maximumMethodsPerDotNetType then + errorR(Error(FSComp.SR.tooManyMethodsInDotNetTypeWritingAssembly (tdef.Name, count, maximumMethodsPerDotNetType), rangeStartup)) + + GenTypeDefsPass1 (enc@[tdef.Name]) cenv (tdef.NestedTypes.AsList()) and GenTypeDefsPass1 enc cenv tdefs = List.iter (GenTypeDefPass1 enc cenv) tdefs @@ -682,7 +695,8 @@ and GenTypeDefsPass1 enc cenv tdefs = List.iter (GenTypeDefPass1 enc cenv) tdefs //===================================================================== let rec GetIdxForTypeDef cenv key = - try cenv.typeDefs.GetTableEntry key + try + cenv.typeDefs.GetTableEntry key with :? KeyNotFoundException -> let (TdKey (enc, n) ) = key diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt index d0fad76ab36..46fd02464da 100644 --- a/src/Compiler/FSComp.txt +++ b/src/Compiler/FSComp.txt @@ -1738,4 +1738,5 @@ featureReuseSameFieldsInStructUnions,"Share underlying fields in a [] di 3860,chkStaticMembersOnObjectExpressions,"Object expressions cannot implement interfaces with static abstract members or declare static members." 3861,chkTailCallAttrOnNonRec,"The TailCall attribute should only be applied to recursive functions." 3862,parsStaticMemberImcompleteSyntax,"Incomplete declaration of a static construct. Use 'static let','static do','static member' or 'static val' for declaration." -3863,parsExpectingField,"Expecting record field" \ No newline at end of file +3863,parsExpectingField,"Expecting record field" +3864,tooManyMethodsInDotNetTypeWritingAssembly,"The type '%s' has too many methods. Found: '%d', maximum: '%d'" \ No newline at end of file diff --git a/src/Compiler/xlf/FSComp.txt.cs.xlf b/src/Compiler/xlf/FSComp.txt.cs.xlf index 07cccc56186..20ffb4de64a 100644 --- a/src/Compiler/xlf/FSComp.txt.cs.xlf +++ b/src/Compiler/xlf/FSComp.txt.cs.xlf @@ -1457,6 +1457,11 @@ Deklarování \"interfaces with static abstract methods\" (rozhraní se statickými abstraktními metodami) je pokročilá funkce. Pokyny najdete v https://aka.ms/fsharp-iwsams. Toto upozornění můžete zakázat pomocí #nowarn \"3535\" nebo '--nowarn:3535'. + + The type '{0}' has too many methods. Found: '{1}', maximum: '{2}' + The type '{0}' has too many methods. Found: '{1}', maximum: '{2}' + + Interface member '{0}' does not have a most specific implementation. Člen rozhraní {0} nemá nejvíce specifickou implementaci. diff --git a/src/Compiler/xlf/FSComp.txt.de.xlf b/src/Compiler/xlf/FSComp.txt.de.xlf index d9def2b9c6a..6844c4ebe7b 100644 --- a/src/Compiler/xlf/FSComp.txt.de.xlf +++ b/src/Compiler/xlf/FSComp.txt.de.xlf @@ -1457,6 +1457,11 @@ Das Deklarieren von \"Schnittstellen mit statischen abstrakten Methoden\" ist ein erweitertes Feature. Anleitungen finden Sie unter https://aka.ms/fsharp-iwsams. Sie können diese Warnung deaktivieren, indem Sie „#nowarn \"3535\"“ or „--nowarn:3535“ verwenden. + + The type '{0}' has too many methods. Found: '{1}', maximum: '{2}' + The type '{0}' has too many methods. Found: '{1}', maximum: '{2}' + + Interface member '{0}' does not have a most specific implementation. Der Schnittstellenmember "{0}" weist keine spezifischste Implementierung auf. diff --git a/src/Compiler/xlf/FSComp.txt.es.xlf b/src/Compiler/xlf/FSComp.txt.es.xlf index 6a6959dc583..797d8231cfd 100644 --- a/src/Compiler/xlf/FSComp.txt.es.xlf +++ b/src/Compiler/xlf/FSComp.txt.es.xlf @@ -1457,6 +1457,11 @@ Declarar \"interfaces con métodos abstractos estáticos\" es una característica avanzada. Consulte https://aka.ms/fsharp-iwsams para obtener instrucciones. Puede deshabilitar esta advertencia con "#nowarn \"3535\"" o "--nowarn:3535". + + The type '{0}' has too many methods. Found: '{1}', maximum: '{2}' + The type '{0}' has too many methods. Found: '{1}', maximum: '{2}' + + Interface member '{0}' does not have a most specific implementation. El miembro de interfaz "{0}" no tiene una implementación más específica. diff --git a/src/Compiler/xlf/FSComp.txt.fr.xlf b/src/Compiler/xlf/FSComp.txt.fr.xlf index 8bf1b97e9bc..c3a07f32303 100644 --- a/src/Compiler/xlf/FSComp.txt.fr.xlf +++ b/src/Compiler/xlf/FSComp.txt.fr.xlf @@ -1457,6 +1457,11 @@ La déclaration de \"interfaces with static abstract methods\" est une fonctionnalité avancée. Consultez https://aka.ms/fsharp-iwsams pour obtenir de l’aide. Vous pouvez désactiver cet avertissement à l’aide de '#nowarn \"3535\"' or '--nowarn:3535'. + + The type '{0}' has too many methods. Found: '{1}', maximum: '{2}' + The type '{0}' has too many methods. Found: '{1}', maximum: '{2}' + + Interface member '{0}' does not have a most specific implementation. Le membre d'interface '{0}' n'a pas l'implémentation la plus spécifique. diff --git a/src/Compiler/xlf/FSComp.txt.it.xlf b/src/Compiler/xlf/FSComp.txt.it.xlf index 135dbc86cf4..1bbd6f3a48e 100644 --- a/src/Compiler/xlf/FSComp.txt.it.xlf +++ b/src/Compiler/xlf/FSComp.txt.it.xlf @@ -1457,6 +1457,11 @@ La dichiarazione di \"interfaces with static abstract methods\" è una funzionalità avanzata. Per indicazioni, vedere https://aka.ms/fsharp-iwsams. È possibile disabilitare questo avviso usando '#nowarn \"3535\"' o '--nowarn:3535'. + + The type '{0}' has too many methods. Found: '{1}', maximum: '{2}' + The type '{0}' has too many methods. Found: '{1}', maximum: '{2}' + + Interface member '{0}' does not have a most specific implementation. Il membro di interfaccia '{0}' non contiene un'implementazione più specifica. diff --git a/src/Compiler/xlf/FSComp.txt.ja.xlf b/src/Compiler/xlf/FSComp.txt.ja.xlf index c4e1d71a82f..254548c4d61 100644 --- a/src/Compiler/xlf/FSComp.txt.ja.xlf +++ b/src/Compiler/xlf/FSComp.txt.ja.xlf @@ -1457,6 +1457,11 @@ \"interfaces with static abstract method\" の宣言は高度な機能です。ガイダンスについては https://aka.ms/fsharp-iwsams を参照してください。この警告は、'#nowarn \"3535\"' または '--nowarn:3535' を使用して無効にできます。 + + The type '{0}' has too many methods. Found: '{1}', maximum: '{2}' + The type '{0}' has too many methods. Found: '{1}', maximum: '{2}' + + Interface member '{0}' does not have a most specific implementation. インターフェイス メンバー '{0}' には最も固有な実装がありません。 diff --git a/src/Compiler/xlf/FSComp.txt.ko.xlf b/src/Compiler/xlf/FSComp.txt.ko.xlf index 4751ad79ebc..f9fbab289f2 100644 --- a/src/Compiler/xlf/FSComp.txt.ko.xlf +++ b/src/Compiler/xlf/FSComp.txt.ko.xlf @@ -1457,6 +1457,11 @@ \"interfaces with static abstract methods\"를 선언하는 것은 고급 기능입니다. 지침은 https://aka.ms/fsharp-iwsams를 참조하세요. '#nowarn \"3535\"' 또는 '--nowarn:3535'를 사용하여 이 경고를 비활성화할 수 있습니다. + + The type '{0}' has too many methods. Found: '{1}', maximum: '{2}' + The type '{0}' has too many methods. Found: '{1}', maximum: '{2}' + + Interface member '{0}' does not have a most specific implementation. 인터페이스 멤버 '{0}'에 가장 한정적인 구현이 없습니다. diff --git a/src/Compiler/xlf/FSComp.txt.pl.xlf b/src/Compiler/xlf/FSComp.txt.pl.xlf index 81d62979a4e..075f8c0cc06 100644 --- a/src/Compiler/xlf/FSComp.txt.pl.xlf +++ b/src/Compiler/xlf/FSComp.txt.pl.xlf @@ -1457,6 +1457,11 @@ Deklarowanie \"interfejsów ze statycznymi metodami abstrakcyjnymi\" jest funkcją zaawansowaną. Aby uzyskać wskazówki, zobacz https://aka.ms/fsharp-iwsams. To ostrzeżenie można wyłączyć przy użyciu polecenia „#nowarn \"3535\"" lub "--nowarn:3535”. + + The type '{0}' has too many methods. Found: '{1}', maximum: '{2}' + The type '{0}' has too many methods. Found: '{1}', maximum: '{2}' + + Interface member '{0}' does not have a most specific implementation. Składowa interfejsu „{0}” nie ma najbardziej specyficznej implementacji. diff --git a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf index 8cdbcdb29c4..2fc380b9748 100644 --- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf +++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf @@ -1457,6 +1457,11 @@ Declarando \"interfaces com métodos abstratos estáticos\" é um recurso avançado. Consulte https://aka.ms/fsharp-iwsams para obter diretrizes. Você pode desabilitar esse aviso usando '#nowarn \"3535\"' ou '--nowarn:3535'. + + The type '{0}' has too many methods. Found: '{1}', maximum: '{2}' + The type '{0}' has too many methods. Found: '{1}', maximum: '{2}' + + Interface member '{0}' does not have a most specific implementation. O membro de interface '{0}' não tem uma implementação mais específica. diff --git a/src/Compiler/xlf/FSComp.txt.ru.xlf b/src/Compiler/xlf/FSComp.txt.ru.xlf index 1319210fbc9..85f28ad43be 100644 --- a/src/Compiler/xlf/FSComp.txt.ru.xlf +++ b/src/Compiler/xlf/FSComp.txt.ru.xlf @@ -1457,6 +1457,11 @@ Объявление \"интерфейсов со статическими абстрактными методами\" является расширенной функцией. См. руководство на https://aka.ms/fsharp-iwsams. Это предупреждение можно отключить с помощью используя "#nowarn \"3535\"" or "--nowarn:3535". + + The type '{0}' has too many methods. Found: '{1}', maximum: '{2}' + The type '{0}' has too many methods. Found: '{1}', maximum: '{2}' + + Interface member '{0}' does not have a most specific implementation. Элемент интерфейса "{0}" не имеет наиболее конкретной реализации. diff --git a/src/Compiler/xlf/FSComp.txt.tr.xlf b/src/Compiler/xlf/FSComp.txt.tr.xlf index d9143a0db5d..843ad544e09 100644 --- a/src/Compiler/xlf/FSComp.txt.tr.xlf +++ b/src/Compiler/xlf/FSComp.txt.tr.xlf @@ -1457,6 +1457,11 @@ \"interfaces with static abstract methods\" bildirimi gelişmiş bir özelliktir. Rehber için bkz. https://aka.ms/fsharp-iwsams. '#nowarn \"3535\"' veya '--nowarn:3535'. kullanarak bu uyarıyı devre dışı bırakabilirsiniz. + + The type '{0}' has too many methods. Found: '{1}', maximum: '{2}' + The type '{0}' has too many methods. Found: '{1}', maximum: '{2}' + + Interface member '{0}' does not have a most specific implementation. '{0}' arabirim üyesinin en belirgin uygulaması yok. diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf index c62c1e73f6c..9c5c4fc8857 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf @@ -1457,6 +1457,11 @@ 声明“使用静态抽象方法的接口”是一项高级功能。有关指南,请参阅 https://aka.ms/fsharp-iwsams。可以使用 "#nowarn \"3535\"' 或 '--nowarn:3535' 禁用此警告。 + + The type '{0}' has too many methods. Found: '{1}', maximum: '{2}' + The type '{0}' has too many methods. Found: '{1}', maximum: '{2}' + + Interface member '{0}' does not have a most specific implementation. 接口成员“{0}”没有最具体的实现。 diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf index 5f66fcae4ea..6992ddfd8b6 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf @@ -1457,6 +1457,11 @@ 使用「靜態抽象方法宣告介面」是進階的功能。請參閱 https://aka.ms/fsharp-iwsams 以尋求指引。您可以使用 '#nowarn \"3535\"' 或 '--nowarn:3535' 來停用此警告。 + + The type '{0}' has too many methods. Found: '{1}', maximum: '{2}' + The type '{0}' has too many methods. Found: '{1}', maximum: '{2}' + + Interface member '{0}' does not have a most specific implementation. 介面成員 '{0}' 沒有最具體的實作。 diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/VeryLargeClasses.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/VeryLargeClasses.fs new file mode 100644 index 00000000000..f8924fca551 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/VeryLargeClasses.fs @@ -0,0 +1,50 @@ +namespace EmittedIL + +open Microsoft.FSharp.Core +open Xunit +open FSharp.Test.Compiler + +module VeryLargeClasses = + + let classWithManyMethods n = + let methods = + let mutable source = "" + for i = 0 to n - 1 do + source <- source + $""" + static member Method%05x{i}() = () """ + source + + FSharp + $""" + namespace VeryLargeClassesTestcases + + type Type1 ={methods} + """ + + [] + let ``More than 64K Methods -- should fail`` () = + classWithManyMethods (1024 * 64 + 1) + |> compile + |> shouldFail + |> withDiagnostics [ + (Error 3864, Line 1, Col 1, Line 1, Col 1, "The type 'VeryLargeClassesTestcases.Type1' has too many methods. Found: '65537', maximum: '65520'") + ] + + [] + let ``Exactly (0xfff0) Methods -- should succeed`` () = + FSharp + """ +module MyMain +open System +open System.Reflection +do printfn $"location: {typeof.Assembly.Location}" +let asm = Assembly.LoadFrom(typeof.Assembly.Location) +printfn $"asm: {asm}" +let types = asm.GetTypes() +printfn "length: {types.Length}" +""" + |> withReferences [ classWithManyMethods 0xfff0 |> asLibrary ] + |> asExe + |> compileAndRun + |> shouldSucceed + diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index 70221cde693..a46a34a1160 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -124,6 +124,7 @@ + diff --git a/tests/FSharp.Test.Utilities/CompilerAssert.fs b/tests/FSharp.Test.Utilities/CompilerAssert.fs index 1dcbcb846ff..b5e12d8cd5a 100644 --- a/tests/FSharp.Test.Utilities/CompilerAssert.fs +++ b/tests/FSharp.Test.Utilities/CompilerAssert.fs @@ -386,7 +386,7 @@ module rec CompilerAssertHelpers = let name = match nameOpt with | Some name -> name - | _ -> tryCreateTemporaryFileName() + | _ -> tryCreateTemporaryFileNameInDirectory(outputDirectory) let outputFilePath = Path.ChangeExtension (Path.Combine(outputDirectory.FullName, name), if isExe then ".exe" else ".dll") disposals.Add(disposeFile outputFilePath) diff --git a/tests/FSharp.Test.Utilities/TestFramework.fs b/tests/FSharp.Test.Utilities/TestFramework.fs index e5af95432b0..f7ec7f48001 100644 --- a/tests/FSharp.Test.Utilities/TestFramework.fs +++ b/tests/FSharp.Test.Utilities/TestFramework.fs @@ -28,12 +28,11 @@ let tryCreateTemporaryFileName () = filePath // Create a temporaryFileName -- newGuid is random --- there is no point validating the file alread exists because: threading and Path.ChangeExtension() is commonly used after this API -let tryCreateTemporaryFileNameInDirectory (directory) = +let tryCreateTemporaryFileNameInDirectory (directory: DirectoryInfo) = let fileName = ("Temp-" + Guid.NewGuid().ToString() + ".tmp").Replace('-', '_') - let filePath = Path.Combine(directory, fileName) + let filePath = Path.Combine(directory.FullName, fileName) filePath - [] module Commands = diff --git a/tests/service/ScriptOptionsTests.fs b/tests/service/ScriptOptionsTests.fs index 6b692543572..4cdeaa5c3c2 100644 --- a/tests/service/ScriptOptionsTests.fs +++ b/tests/service/ScriptOptionsTests.fs @@ -50,9 +50,9 @@ let ``can generate options for different frameworks regardless of execution envi [] [] let ``can resolve nuget packages to right target framework for different frameworks regardless of execution environment``(flags) = - let path = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + let path = DirectoryInfo(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)) let file = tryCreateTemporaryFileNameInDirectory(path) + ".fsx" - let scriptFullPath = Path.Combine(path, file) + let scriptFullPath = Path.Combine(path.FullName, file) let scriptSource = """ #r "nuget: FSharp.Data, 3.3.3" open System