Allow let to not have value when using importc (#14258)

* Allow let to not have value when using importc

This allows a let statement with the `{.importc.}` pragma to not be
initialised with a value. This allows us to declare C constants as Nim
lets without putting the value in the Nim code (which can lead to
errors, and requires us to go looking for the value). Fixes #14253

* Proper fix and documentation + changelog entry

* Improve testcase with one from timotheecour

* Add test to verify it working with macros
This commit is contained in:
PMunch 2020-05-12 20:28:18 +02:00 • committed by GitHub
commit 9acbf99efb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 52 additions and 5 deletions

View file

@ -122,11 +122,11 @@
deallocShared(x.val) deallocShared(x.val)
x.val = nil x.val = nil
``` ```
- getImpl() on enum type symbols now returns field syms instead of idents. This helps - getImpl() on enum type symbols now returns field syms instead of idents. This helps
with writing typed macros. Old behavior for backwards compatiblity can be restored with writing typed macros. Old behavior for backwards compatiblity can be restored
with command line switch `--useVersion:1.0`. with command line switch `--useVersion:1.0`.
- ``let`` statements can now be used without a value if declared with
``importc``/``importcpp``/``importjs``/``importobjc``.
- The keyword `from` is now usable as an operator. - The keyword `from` is now usable as an operator.
- Exceptions inheriting from `system.Defect` are no longer tracked with - Exceptions inheriting from `system.Defect` are no longer tracked with
the `.raises: []` exception tracking mechanism. This is more consistent with the the `.raises: []` exception tracking mechanism. This is more consistent with the
@ -152,7 +152,6 @@ proc mydiv(a, b): int {.raises: [].} =
The reason for this is that `DivByZeroDefect` inherits from `Defect` and The reason for this is that `DivByZeroDefect` inherits from `Defect` and
with `--panics:on` `Defects` become unrecoverable errors. with `--panics:on` `Defects` become unrecoverable errors.
## Compiler changes ## Compiler changes
- Specific warnings can now be turned into errors via `--warningAsError[X]:on|off`. - Specific warnings can now be turned into errors via `--warningAsError[X]:on|off`.

View file

@ -531,8 +531,6 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
# special type inference rule: 'var it = ownedPointer' is turned # special type inference rule: 'var it = ownedPointer' is turned
# into an unowned pointer. # into an unowned pointer.
typ = typ.lastSon typ = typ.lastSon
else:
if symkind == skLet: localError(c.config, a.info, errLetNeedsInit)
# this can only happen for errornous var statements: # this can only happen for errornous var statements:
if typ == nil: continue if typ == nil: continue
@ -620,6 +618,9 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
defaultConstructionError(c, v.typ, v.info) defaultConstructionError(c, v.typ, v.info)
else: else:
checkNilable(c, v) checkNilable(c, v)
# allow let to not be initialised if imported from C:
if v.kind == skLet and sfImportc notin v.flags:
localError(c.config, a.info, errLetNeedsInit)
if sfCompileTime in v.flags: if sfCompileTime in v.flags:
var x = newNodeI(result.kind, v.info) var x = newNodeI(result.kind, v.info)
x.add result[i] x.add result[i]

View file

@ -2679,6 +2679,11 @@ nor can their address be taken. They cannot be assigned new values.
For let variables the same pragmas are available as for ordinary variables. For let variables the same pragmas are available as for ordinary variables.
As ``let`` statements are immutable after creation they need to define a value
when they are declared. The only exception to this is if the ``{.importc.}``
pragma (or any of the other ``importX`` pragmas) is applied, in this case the
value is expected to come from native code, typically a C/C++ ``const``.
Tuple unpacking Tuple unpacking
--------------- ---------------
@ -7090,6 +7095,16 @@ spelled*:
.. code-block:: .. code-block::
proc printf(formatstr: cstring) {.header: "<stdio.h>", importc: "printf", varargs.} proc printf(formatstr: cstring) {.header: "<stdio.h>", importc: "printf", varargs.}
When ``importc`` is applied to a ``let`` statement it can omit its value which
will then be expected to come from C. This can be used to import a C ``const``:
.. code-block::
{.emit: "const int cconst = 42;".}
let cconst {.importc, nodecl.}: cint
assert cconst == 42
Note that this pragma has been abused in the past to also work in the Note that this pragma has been abused in the past to also work in the
js backend for js objects and functions. : Other backends do provide js backend for js objects and functions. : Other backends do provide
the same feature under the same name. Also, when the target language the same feature under the same name. Also, when the target language

24
tests/let/timportc.nim Normal file
View file

@ -0,0 +1,24 @@
discard """
targets: "c cpp js"
"""
when defined(c) or defined(cpp):
{.emit:"""
const int TEST1 = 123;
#define TEST2 321
""".}
when defined(js):
{.emit:"""
const TEST1 = 123;
const TEST2 = 321; // JS doesn't have macros, so we just duplicate
""".}
let
TEST0 = 1
TEST1 {.importc, nodecl.}: cint
TEST2 {.importc, nodecl.}: cint
doAssert TEST0 == 1
doAssert TEST1 == 123
doAssert TEST2 == 321

8
tests/let/timportc2.nim Normal file
View file

@ -0,0 +1,8 @@
discard """
errormsg: "'let' symbol requires an initialization"
line: "7"
"""
# Test that this still works when not annotated with importc
let test: cint
echo test