Tcl Source Code

View Ticket
Bounty program for improvements to Tcl and certain Tcl packages.
Ticket UUID: 1b0266d8bbc649bcb4df5dc4a1edca238536262b
Title: Single-argument [dict merge/remove/replace]
Type: Bug Version: cdc4e05235717e4959d745e4cc6670bd1333c783
Submitter: andy Created on: 2014-05-21 22:58:03
Subsystem: 15. Dict Object Assigned To: dkf
Priority: 5 Medium Severity: Minor
Status: Closed Last Modified: 2014-06-15 16:15:11
Resolution: Fixed Closed By: dkf
    Closed on: 2014-06-15 16:15:11
When given only one argument, [dict remove] and [dict replace] seem to return that argument without even looking at it.  Consequently, they can be made to return non-dicts (i.e. invalid list or odd list length) and noncanonical dicts (i.e. with duplicate keys).

When given only one argument, [dict merge] forces the argument to be a dict (thereby rejecting non-lists and odd-length lists), but the string representation is preserved.  Consequently, [dict merge] can be made to return noncanonical dicts.

% dict remove \{\}x
% dict remove {a 1 b}
a 1 b
% dict remove {a 1 b 2 a 3}
a 1 b 2 a 3
% dict replace \{\}x
% dict replace {a 1 b}
a 1 b
% dict replace {a 1 b 2 a 3}
a 1 b 2 a 3
% dict merge \{\}x
list element in braces followed by "x" instead of space
% dict merge {a 1 b}
missing value to go with key
% dict merge {a 1 b 2 a 3}
a 1 b 2 a 3
% ::tcl::unsupported::representation [dict merge {a 1 b 2}]
value is a dict with a refcount of 3, object pointer at 0xbeb3b0, internal representation 0xbe11a0:0xbeb860, string representation "a 1 b 2"
% ::tcl::unsupported::representation [dict merge {a 1 b 2 a 3}]
value is a dict with a refcount of 5, object pointer at 0x1c3a560, internal representation 0x1c843a0:0x1c35790, string representation "a 1 b 2 a 3"

Contrast with every other [dict] command that returns a dict and/or sets a dict variable.  They all produce only canonical dicts.

% dict get {a 1 b 2 a 3}
a 3 b 2
% ::tcl::unsupported::representation [dict get {a 1 b 2 a 3}]
value is a list with a refcount of 1, object pointer at 0x99cc90, internal representation 0x9adb00:(nil), no string representation
% ::tcl::unsupported::representation [dict get {a 1 b 2}]
value is a list with a refcount of 1, object pointer at 0x9a09e0, internal representation 0x9db0e0:(nil), no string representation
User Comments: dkf added on 2014-06-15 15:36:27:

In fact, I completely refuse to promise canonicalization with dict merge, as that causes problems in compilation.

dkf added on 2014-06-15 07:59:29:

On the other hand, having dict replace and dict remove guarantee canonicality in their result (as long as it doesn't leak back into the argument) is reasonable; they parallel with lrange's features in this area.

I think dict merge doesn't need the change, as that feels more like it's working with whatever people provide; it merges your dictionaries.

dkf added on 2014-06-03 07:31:08:

Uncovered an issue when working on this; the error message is wrong.

test dict-4.14 {dict replace command: type check is mandatory} -body {
    dict replace { a b {}c d }
} -returnCodes error -result {list element in braces followed by "c" instead of space}
It's a bit tricky to fix; the message comes out of TclFindElement and that both has a semi-exposed API and no way to actually affect it at the moment.

dkf added on 2014-06-03 07:09:53:

To be exact, canonicality comes from the modification causing a purge of the old string representation. Consider this:

% dict merge {
  a b
  c d
} {} {} {}

  a b
  c d

% dict merge {
  a b
  c d
} {a b}
a b c d
(No, we aren't going to detect where a modification “does nothing”.)

dkf added on 2014-06-03 06:59:39:

I think the non-canonicality is OK; the code isn't guaranteed to canonicalize when it doesn't have anything to do. However, the failure to confirm the format is indeed an (arguable) bug.

It'd be a Potential Incompatibility though.