diff --git a/changelog.md b/changelog.md index 289202587319b..ceb23dbfe8a74 100644 --- a/changelog.md +++ b/changelog.md @@ -391,6 +391,9 @@ - Added a new module `std/importutils`, and an API `privateAccess`, which allows access to private fields for an object type in the current scope. +- APIs can now indicate they've changed their semantics via `macros.migrated`, e.g.: + `{.migrated(nimMigratedGetHomeDir, "`getHomeDir` now does not end in DirSep, see `normalizePathEnd`").}` + - `typeof(voidStmt)` now works and returns `void`. - The `gc:orc` algorithm was refined so that custom container types can participate in the diff --git a/compiler/docgen.nim b/compiler/docgen.nim index 1ae00e22e3afa..eee927aaca774 100644 --- a/compiler/docgen.nim +++ b/compiler/docgen.nim @@ -21,6 +21,7 @@ import from uri import encodeUrl from std/private/globs import nativeToUnixPath from nodejs import findNodeJs +import std/private/constants const exportSection = skField @@ -809,8 +810,13 @@ proc genDeprecationMsg(d: PDoc, n: PNode): string = "label" , "Deprecated", "message", ""] of 2: # Deprecated w/ a message if n[1].kind in {nkStrLit..nkTripleStrLit}: - result = getConfigVar(d.conf, "doc.deprecationmsg") % [ - "label", "Deprecated:", "message", xmltree.escape(n[1].strVal)] + let msg = n[1].strVal + if msg.startsWith migratedPrefix: + result = getConfigVar(d.conf, "doc.deprecationmsg") % [ + "label", "Migrated:", "message", xmltree.escape(msg[migratedPrefix.len..^1])] + else: + result = getConfigVar(d.conf, "doc.deprecationmsg") % [ + "label", "Deprecated:", "message", xmltree.escape(msg)] else: doAssert false diff --git a/compiler/lineinfos.nim b/compiler/lineinfos.nim index 801c20d2557ea..0cb82f59f1e0e 100644 --- a/compiler/lineinfos.nim +++ b/compiler/lineinfos.nim @@ -46,6 +46,7 @@ type warnCannotOpenFile = "CannotOpenFile", warnOctalEscape = "OctalEscape", warnXIsNeverRead = "XIsNeverRead", warnXmightNotBeenInit = "XmightNotBeenInit", warnDeprecated = "Deprecated", warnConfigDeprecated = "ConfigDeprecated", + warnMigrated = "Migrated", warnSmallLshouldNotBeUsed = "SmallLshouldNotBeUsed", warnUnknownMagic = "UnknownMagic", warnRedefinitionOfLabel = "RedefinitionOfLabel", warnUnknownSubstitutionX = "UnknownSubstitutionX", warnLanguageXNotSupported = "LanguageXNotSupported", @@ -109,6 +110,7 @@ const warnXmightNotBeenInit: "'$1' might not have been initialized", warnDeprecated: "$1", warnConfigDeprecated: "config file '$1' is deprecated", + warnMigrated: "$1", warnSmallLshouldNotBeUsed: "'l' should not be used as an identifier; may look like '1' (one)", warnUnknownMagic: "unknown magic '$1' might crash the compiler", warnRedefinitionOfLabel: "redefinition of label '$1'", diff --git a/compiler/suggest.nim b/compiler/suggest.nim index eaa30040a8d30..27bad37877dae 100644 --- a/compiler/suggest.nim +++ b/compiler/suggest.nim @@ -32,7 +32,8 @@ # included from sigmatch.nim -import algorithm, sets, prefixmatches, lineinfos, parseutils, linter, tables +import algorithm, sets, prefixmatches, lineinfos, parseutils, linter, tables, astmsgs +import std/private/constants from wordrecg import wDeprecated, wError, wAddr, wYield when defined(nimsuggest): @@ -547,7 +548,13 @@ proc warnAboutDeprecated(conf: ConfigRef; info: TLineInfo; s: PSym) = for it in pragmaNode: if whichPragma(it) == wDeprecated and it.safeLen == 2 and it[1].kind in {nkStrLit..nkTripleStrLit}: - message(conf, info, warnDeprecated, it[1].strVal & "; " & name & " is deprecated") + let msg = it[1].strVal + if msg.startsWith migratedPrefix: + var msg2 = msg[migratedPrefix.len .. ^1] & "; " & name & " was migrated" + addDeclaredLoc(msg2, conf, s) + message(conf, info, warnMigrated, msg2) + else: + message(conf, info, warnDeprecated, msg & "; " & name & " is deprecated") return message(conf, info, warnDeprecated, name & " is deprecated") diff --git a/config/config.nims b/config/config.nims index 2ae86012c68fe..aa8f8880f6ee4 100644 --- a/config/config.nims +++ b/config/config.nims @@ -14,3 +14,7 @@ when defined(nimStrictMode): # switch("hint", "ConvFromXtoItselfNotNeeded") switch("hintAsError", "ConvFromXtoItselfNotNeeded") # future work: XDeclaredButNotUsed + +switch("define", "nimMigratedGetHomeDir") +switch("define", "nimMigratedGetConfigDir") +switch("define", "nimMigratedGetTempDir") diff --git a/lib/core/macros.nim b/lib/core/macros.nim index c09fae6b3ac0e..c01704da839cc 100644 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -8,7 +8,7 @@ # include "system/inclrtl" -import std/private/since +import std/private/[since, constants] ## This module contains the interface to the compiler's abstract syntax ## tree (`AST`:idx:). Macros operate on this tree. @@ -1800,3 +1800,23 @@ proc extractDocCommentsAndRunnables*(n: NimNode): NimNode = result.add ni else: break else: break + +macro customImpl(msg, body): untyped = + result = body + addPragma(result, newCall(ident"deprecated", msg)) + +template migrated*(ident, msg, body): untyped = + ## Conditionally generate a migration warning for APIs that were migrated to + ## different semantics, to help existing code migrate. + runnableExamples("--warningAsError:migrated -d:nimMigratedExample1"): + proc fn*(a: int) {.migrated(nimMigratedExample1, "fn can now raise a Defect").} = + doAssert a >= 0 + assert not compiles(fn(10)) # would generate a warning (and ) + runnableExamples("--warningAsError:migrated"): + # With `-d:nimMigratedExample1`, would generate: + # Warning: fn can now raise a Defect; fn was migrated [proc declared in ...] [Migrated] + proc fn*(a: int) {.migrated(nimMigratedExample1, "fn can now raise a Defect").} = + doAssert a >= 0 + fn(10) + when defined(ident): customImpl(migratedPrefix & msg, body) + else: body diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 213da2aedff99..ff374f4241868 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -44,7 +44,7 @@ include system/inclrtl import std/private/since -import strutils, pathnorm +import strutils, pathnorm, macros const weirdTarget = defined(nimscript) or defined(js) @@ -886,8 +886,8 @@ include "includes/oserr" when not defined(nimscript): include "includes/osenv" -proc getHomeDir*(): string {.rtl, extern: "nos$1", - tags: [ReadEnvEffect, ReadIOEffect].} = +proc getHomeDir*(): string {.rtl, extern: "nos$1", tags: [ReadEnvEffect, ReadIOEffect], + migrated(nimMigratedGetHomeDir, "`getHomeDir` now does not end in DirSep, see `normalizePathEnd`").} = ## Returns the home directory of the current user. ## ## This proc is wrapped by the `expandTilde proc <#expandTilde,string>`_ @@ -910,8 +910,8 @@ proc getHomeDir*(): string {.rtl, extern: "nos$1", else: result = getEnv("HOME") result.normalizePathEnd(trailingSep = defined(nimLegacyHomeDir)) -proc getConfigDir*(): string {.rtl, extern: "nos$1", - tags: [ReadEnvEffect, ReadIOEffect].} = +proc getConfigDir*(): string {.rtl, extern: "nos$1", tags: [ReadEnvEffect, ReadIOEffect], + migrated(nimMigratedGetConfigDir, "`getConfigDir` now does not end in DirSep, see `normalizePathEnd`").} = ## Returns the config directory of the current user for applications. ## ## On non-Windows OSs, this proc conforms to the XDG Base Directory @@ -989,8 +989,8 @@ template getTempDirImpl(result: var string) = else: getEnvImpl(result, ["TMPDIR", "TEMP", "TMP", "TEMPDIR"]) -proc getTempDir*(): string {.rtl, extern: "nos$1", - tags: [ReadEnvEffect, ReadIOEffect].} = +proc getTempDir*(): string {.rtl, extern: "nos$1", tags: [ReadEnvEffect, ReadIOEffect], + migrated(nimMigratedGetTempDir, "`getTempDir` now does not end in DirSep, see `normalizePathEnd`").} = ## Returns the temporary directory of the current user for applications to ## save temporary files in. ## diff --git a/lib/std/private/constants.nim b/lib/std/private/constants.nim new file mode 100644 index 0000000000000..ae76e4bb85259 --- /dev/null +++ b/lib/std/private/constants.nim @@ -0,0 +1,5 @@ +##[ +This module includes constants that are needed across modules, to avoid redefining them. +]## + +const migratedPrefix* = "migrated: "