`Proxy` confusing `this[toString]` with `this[Symbol.toStringTag]`












3















It only happens with #toString, and only when I (try to) access it through a missingMethod-like trap.



I have a factory called createIterface which returns a Proxy of an object with a large number of methods. Among this methods, I have both #toString() and #id(). #id returns an interface with the same attributes as the caller and works just fine; #toString should convert my interface to a String, but it fails.
All interface's methods - including #id and #toString - are inside a #Symbol.for("__methods") attribute. I have made it this way for debugging's purpouses:



const __methods = Symbol.for("__methods");

const missingMethod = ({
get: (obj, prop) => Reflect.has(obj, prop)
? Reflect.get(obj, prop)
: Reflect.has(obj[__methods], prop)
? Reflect.get(obj[__methods], prop)
: console.log(`No #${prop} property exists.`)
});

const createInterface = (...props) => new Proxy({
...props,
[__methods]: {
id: () => createInterface (...props),
toString: () => `Interface(${ props.toString() })`
}
}, missingMethod);

const interface = createInterface(0, 1, 2);
interface.id(); //works
interface.toString(); //error: Cannot convert a Symbol value to a string


The error throwed says it cannot (implicitly) convert Symbol to String (which is true). Thing is, #toString is not a Symbol. There is, however, a well-known Symbol called #toStringTag that defines Object#toString() behavior. When I implement it with the other methods my #toString() is ignored and interface returns '[object Object]':



// see code above
const createInterface = (...props) => new Proxy({
...props,
[__methods]: {
id: () => createInterface (...props),
toString: () => `Interface(${ props.toString() })`,
[Symbol.toStringTag]: () => "Interface"
}
}, missingMethod);

const interface = createInterface(0, 1, 2);
interface.id(); //works
interface.toString(); //bug: '[object Object]'


If I code the methods outside __methods it all works fine:



// see code above
const createInterface = (...props) => new Proxy({
...props,
id: () => createInterface (...props),
toString: () => `Interface(${ props.toString() })`
}, missingMethod);

const interface = createInterface(0, 1, 2);
const copycat = interface.id();
interface.toString() === copycat.toString(); //true


Other than some weird browse bug (I'm running latest Chrome, which in day of this writing is v. 71.0.3578.98) I have no idea why this is happening or how to fix it.



Could someone help?










share|improve this question





























    3















    It only happens with #toString, and only when I (try to) access it through a missingMethod-like trap.



    I have a factory called createIterface which returns a Proxy of an object with a large number of methods. Among this methods, I have both #toString() and #id(). #id returns an interface with the same attributes as the caller and works just fine; #toString should convert my interface to a String, but it fails.
    All interface's methods - including #id and #toString - are inside a #Symbol.for("__methods") attribute. I have made it this way for debugging's purpouses:



    const __methods = Symbol.for("__methods");

    const missingMethod = ({
    get: (obj, prop) => Reflect.has(obj, prop)
    ? Reflect.get(obj, prop)
    : Reflect.has(obj[__methods], prop)
    ? Reflect.get(obj[__methods], prop)
    : console.log(`No #${prop} property exists.`)
    });

    const createInterface = (...props) => new Proxy({
    ...props,
    [__methods]: {
    id: () => createInterface (...props),
    toString: () => `Interface(${ props.toString() })`
    }
    }, missingMethod);

    const interface = createInterface(0, 1, 2);
    interface.id(); //works
    interface.toString(); //error: Cannot convert a Symbol value to a string


    The error throwed says it cannot (implicitly) convert Symbol to String (which is true). Thing is, #toString is not a Symbol. There is, however, a well-known Symbol called #toStringTag that defines Object#toString() behavior. When I implement it with the other methods my #toString() is ignored and interface returns '[object Object]':



    // see code above
    const createInterface = (...props) => new Proxy({
    ...props,
    [__methods]: {
    id: () => createInterface (...props),
    toString: () => `Interface(${ props.toString() })`,
    [Symbol.toStringTag]: () => "Interface"
    }
    }, missingMethod);

    const interface = createInterface(0, 1, 2);
    interface.id(); //works
    interface.toString(); //bug: '[object Object]'


    If I code the methods outside __methods it all works fine:



    // see code above
    const createInterface = (...props) => new Proxy({
    ...props,
    id: () => createInterface (...props),
    toString: () => `Interface(${ props.toString() })`
    }, missingMethod);

    const interface = createInterface(0, 1, 2);
    const copycat = interface.id();
    interface.toString() === copycat.toString(); //true


    Other than some weird browse bug (I'm running latest Chrome, which in day of this writing is v. 71.0.3578.98) I have no idea why this is happening or how to fix it.



    Could someone help?










    share|improve this question



























      3












      3








      3


      1






      It only happens with #toString, and only when I (try to) access it through a missingMethod-like trap.



      I have a factory called createIterface which returns a Proxy of an object with a large number of methods. Among this methods, I have both #toString() and #id(). #id returns an interface with the same attributes as the caller and works just fine; #toString should convert my interface to a String, but it fails.
      All interface's methods - including #id and #toString - are inside a #Symbol.for("__methods") attribute. I have made it this way for debugging's purpouses:



      const __methods = Symbol.for("__methods");

      const missingMethod = ({
      get: (obj, prop) => Reflect.has(obj, prop)
      ? Reflect.get(obj, prop)
      : Reflect.has(obj[__methods], prop)
      ? Reflect.get(obj[__methods], prop)
      : console.log(`No #${prop} property exists.`)
      });

      const createInterface = (...props) => new Proxy({
      ...props,
      [__methods]: {
      id: () => createInterface (...props),
      toString: () => `Interface(${ props.toString() })`
      }
      }, missingMethod);

      const interface = createInterface(0, 1, 2);
      interface.id(); //works
      interface.toString(); //error: Cannot convert a Symbol value to a string


      The error throwed says it cannot (implicitly) convert Symbol to String (which is true). Thing is, #toString is not a Symbol. There is, however, a well-known Symbol called #toStringTag that defines Object#toString() behavior. When I implement it with the other methods my #toString() is ignored and interface returns '[object Object]':



      // see code above
      const createInterface = (...props) => new Proxy({
      ...props,
      [__methods]: {
      id: () => createInterface (...props),
      toString: () => `Interface(${ props.toString() })`,
      [Symbol.toStringTag]: () => "Interface"
      }
      }, missingMethod);

      const interface = createInterface(0, 1, 2);
      interface.id(); //works
      interface.toString(); //bug: '[object Object]'


      If I code the methods outside __methods it all works fine:



      // see code above
      const createInterface = (...props) => new Proxy({
      ...props,
      id: () => createInterface (...props),
      toString: () => `Interface(${ props.toString() })`
      }, missingMethod);

      const interface = createInterface(0, 1, 2);
      const copycat = interface.id();
      interface.toString() === copycat.toString(); //true


      Other than some weird browse bug (I'm running latest Chrome, which in day of this writing is v. 71.0.3578.98) I have no idea why this is happening or how to fix it.



      Could someone help?










      share|improve this question
















      It only happens with #toString, and only when I (try to) access it through a missingMethod-like trap.



      I have a factory called createIterface which returns a Proxy of an object with a large number of methods. Among this methods, I have both #toString() and #id(). #id returns an interface with the same attributes as the caller and works just fine; #toString should convert my interface to a String, but it fails.
      All interface's methods - including #id and #toString - are inside a #Symbol.for("__methods") attribute. I have made it this way for debugging's purpouses:



      const __methods = Symbol.for("__methods");

      const missingMethod = ({
      get: (obj, prop) => Reflect.has(obj, prop)
      ? Reflect.get(obj, prop)
      : Reflect.has(obj[__methods], prop)
      ? Reflect.get(obj[__methods], prop)
      : console.log(`No #${prop} property exists.`)
      });

      const createInterface = (...props) => new Proxy({
      ...props,
      [__methods]: {
      id: () => createInterface (...props),
      toString: () => `Interface(${ props.toString() })`
      }
      }, missingMethod);

      const interface = createInterface(0, 1, 2);
      interface.id(); //works
      interface.toString(); //error: Cannot convert a Symbol value to a string


      The error throwed says it cannot (implicitly) convert Symbol to String (which is true). Thing is, #toString is not a Symbol. There is, however, a well-known Symbol called #toStringTag that defines Object#toString() behavior. When I implement it with the other methods my #toString() is ignored and interface returns '[object Object]':



      // see code above
      const createInterface = (...props) => new Proxy({
      ...props,
      [__methods]: {
      id: () => createInterface (...props),
      toString: () => `Interface(${ props.toString() })`,
      [Symbol.toStringTag]: () => "Interface"
      }
      }, missingMethod);

      const interface = createInterface(0, 1, 2);
      interface.id(); //works
      interface.toString(); //bug: '[object Object]'


      If I code the methods outside __methods it all works fine:



      // see code above
      const createInterface = (...props) => new Proxy({
      ...props,
      id: () => createInterface (...props),
      toString: () => `Interface(${ props.toString() })`
      }, missingMethod);

      const interface = createInterface(0, 1, 2);
      const copycat = interface.id();
      interface.toString() === copycat.toString(); //true


      Other than some weird browse bug (I'm running latest Chrome, which in day of this writing is v. 71.0.3578.98) I have no idea why this is happening or how to fix it.



      Could someone help?







      javascript google-chrome tostring symbols proxy-pattern






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Dec 30 '18 at 5:00







      Henrique Guerra

















      asked Dec 30 '18 at 4:49









      Henrique GuerraHenrique Guerra

      358




      358
























          1 Answer
          1






          active

          oldest

          votes


















          4














          The problem is that accessing interface.toString first goes through



          get: (obj, prop) => Reflect.has(obj, prop)
          ? Reflect.get(obj, prop)
          : Reflect.has(obj[__methods], prop)
          ...


          You're expecting interface.toString to fall through the ternary here and get to the _methods, but Reflect.has(obj, 'toString') will evaluate to true because of Object.prototype.toString. Then, invoking that function on the object goes through the proxy's getter operation again, searching for a #toStringTag to call. The getter goes through all its ternaries and finds nothing, so it throws on the line



          console.log(`No #${prop} property exists.`)


          because prop is a symbol and cannot be concatenated.



          One possibility would be to use an object that does not inherit from Object.prototype:



          const obj = Object.create(null);
          const createInterface = (...props) => new Proxy(
          Object.assign(obj, {
          ...props,
          [__methods]: {
          id: () => createInterface (...props),
          toString: () => `Interface(${ props.toString() })`
          }
          })
          , missingMethod
          );





          const __methods = Symbol.for("__methods");

          const missingMethod = ({
          get: (obj, prop) => Reflect.has(obj, prop)
          ? Reflect.get(obj, prop)
          : Reflect.has(obj[__methods], prop)
          ? Reflect.get(obj[__methods], prop)
          : console.log(`No #${prop} property exists.`)
          });

          const obj = Object.create(null);
          const createInterface = (...props) => new Proxy(
          Object.assign(obj, {
          ...props,
          [__methods]: {
          id: () => createInterface (...props),
          toString: () => `Interface(${ props.toString() })`
          }
          })
          , missingMethod
          );

          const interface = createInterface(0, 1, 2);
          interface.id(); //works
          console.log(interface.toString());





          Another possibility would be for the getter to do a hasOwnProperty check instead of a Reflect.has check (Reflect.has is basically the same as in, and 'toString' will be in almost any object):



          get: (obj, prop) => obj.hasOwnProperty(prop)





          const __methods = Symbol.for("__methods");

          const missingMethod = ({
          get: (obj, prop) => obj.hasOwnProperty(prop)
          ? Reflect.get(obj, prop)
          : Reflect.has(obj[__methods], prop)
          ? Reflect.get(obj[__methods], prop)
          : console.log(`No #${prop} property exists.`)
          });
          const createInterface = (...props) => new Proxy({
          ...props,
          [__methods]: {
          id: () => createInterface (...props),
          toString: () => `Interface(${ props.toString() })`,
          }
          }, missingMethod);

          const interface = createInterface(0, 1, 2);
          interface.id(); //works
          console.log(interface.toString());





          A third possibility would be to make sure the property found by the initial Reflect.has is not from an Object.prototype method:



          get: (obj, prop) => Reflect.has(obj, prop) && Reflect.get(obj, prop) !== Object.prototype[prop]





          const __methods = Symbol.for("__methods");

          const missingMethod = ({
          get: (obj, prop) => Reflect.has(obj, prop) && Reflect.get(obj, prop) !== Object.prototype[prop]
          ? Reflect.get(obj, prop)
          : Reflect.has(obj[__methods], prop)
          ? Reflect.get(obj[__methods], prop)
          : console.log(`No #${prop} property exists.`)
          });

          const createInterface = (...props) => new Proxy({
          ...props,
          [__methods]: {
          id: () => createInterface (...props),
          toString: () => `Interface(${ props.toString() })`
          }
          }, missingMethod);

          const interface = createInterface(0, 1, 2);
          interface.id(); //works
          console.log(interface.toString());








          share|improve this answer























            Your Answer






            StackExchange.ifUsing("editor", function () {
            StackExchange.using("externalEditor", function () {
            StackExchange.using("snippets", function () {
            StackExchange.snippets.init();
            });
            });
            }, "code-snippets");

            StackExchange.ready(function() {
            var channelOptions = {
            tags: "".split(" "),
            id: "1"
            };
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function() {
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled) {
            StackExchange.using("snippets", function() {
            createEditor();
            });
            }
            else {
            createEditor();
            }
            });

            function createEditor() {
            StackExchange.prepareEditor({
            heartbeatType: 'answer',
            autoActivateHeartbeat: false,
            convertImagesToLinks: true,
            noModals: true,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: 10,
            bindNavPrevention: true,
            postfix: "",
            imageUploader: {
            brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
            contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
            allowUrls: true
            },
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            });


            }
            });














            draft saved

            draft discarded


















            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53975350%2fproxy-confusing-thistostring-with-thissymbol-tostringtag%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown

























            1 Answer
            1






            active

            oldest

            votes








            1 Answer
            1






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes









            4














            The problem is that accessing interface.toString first goes through



            get: (obj, prop) => Reflect.has(obj, prop)
            ? Reflect.get(obj, prop)
            : Reflect.has(obj[__methods], prop)
            ...


            You're expecting interface.toString to fall through the ternary here and get to the _methods, but Reflect.has(obj, 'toString') will evaluate to true because of Object.prototype.toString. Then, invoking that function on the object goes through the proxy's getter operation again, searching for a #toStringTag to call. The getter goes through all its ternaries and finds nothing, so it throws on the line



            console.log(`No #${prop} property exists.`)


            because prop is a symbol and cannot be concatenated.



            One possibility would be to use an object that does not inherit from Object.prototype:



            const obj = Object.create(null);
            const createInterface = (...props) => new Proxy(
            Object.assign(obj, {
            ...props,
            [__methods]: {
            id: () => createInterface (...props),
            toString: () => `Interface(${ props.toString() })`
            }
            })
            , missingMethod
            );





            const __methods = Symbol.for("__methods");

            const missingMethod = ({
            get: (obj, prop) => Reflect.has(obj, prop)
            ? Reflect.get(obj, prop)
            : Reflect.has(obj[__methods], prop)
            ? Reflect.get(obj[__methods], prop)
            : console.log(`No #${prop} property exists.`)
            });

            const obj = Object.create(null);
            const createInterface = (...props) => new Proxy(
            Object.assign(obj, {
            ...props,
            [__methods]: {
            id: () => createInterface (...props),
            toString: () => `Interface(${ props.toString() })`
            }
            })
            , missingMethod
            );

            const interface = createInterface(0, 1, 2);
            interface.id(); //works
            console.log(interface.toString());





            Another possibility would be for the getter to do a hasOwnProperty check instead of a Reflect.has check (Reflect.has is basically the same as in, and 'toString' will be in almost any object):



            get: (obj, prop) => obj.hasOwnProperty(prop)





            const __methods = Symbol.for("__methods");

            const missingMethod = ({
            get: (obj, prop) => obj.hasOwnProperty(prop)
            ? Reflect.get(obj, prop)
            : Reflect.has(obj[__methods], prop)
            ? Reflect.get(obj[__methods], prop)
            : console.log(`No #${prop} property exists.`)
            });
            const createInterface = (...props) => new Proxy({
            ...props,
            [__methods]: {
            id: () => createInterface (...props),
            toString: () => `Interface(${ props.toString() })`,
            }
            }, missingMethod);

            const interface = createInterface(0, 1, 2);
            interface.id(); //works
            console.log(interface.toString());





            A third possibility would be to make sure the property found by the initial Reflect.has is not from an Object.prototype method:



            get: (obj, prop) => Reflect.has(obj, prop) && Reflect.get(obj, prop) !== Object.prototype[prop]





            const __methods = Symbol.for("__methods");

            const missingMethod = ({
            get: (obj, prop) => Reflect.has(obj, prop) && Reflect.get(obj, prop) !== Object.prototype[prop]
            ? Reflect.get(obj, prop)
            : Reflect.has(obj[__methods], prop)
            ? Reflect.get(obj[__methods], prop)
            : console.log(`No #${prop} property exists.`)
            });

            const createInterface = (...props) => new Proxy({
            ...props,
            [__methods]: {
            id: () => createInterface (...props),
            toString: () => `Interface(${ props.toString() })`
            }
            }, missingMethod);

            const interface = createInterface(0, 1, 2);
            interface.id(); //works
            console.log(interface.toString());








            share|improve this answer




























              4














              The problem is that accessing interface.toString first goes through



              get: (obj, prop) => Reflect.has(obj, prop)
              ? Reflect.get(obj, prop)
              : Reflect.has(obj[__methods], prop)
              ...


              You're expecting interface.toString to fall through the ternary here and get to the _methods, but Reflect.has(obj, 'toString') will evaluate to true because of Object.prototype.toString. Then, invoking that function on the object goes through the proxy's getter operation again, searching for a #toStringTag to call. The getter goes through all its ternaries and finds nothing, so it throws on the line



              console.log(`No #${prop} property exists.`)


              because prop is a symbol and cannot be concatenated.



              One possibility would be to use an object that does not inherit from Object.prototype:



              const obj = Object.create(null);
              const createInterface = (...props) => new Proxy(
              Object.assign(obj, {
              ...props,
              [__methods]: {
              id: () => createInterface (...props),
              toString: () => `Interface(${ props.toString() })`
              }
              })
              , missingMethod
              );





              const __methods = Symbol.for("__methods");

              const missingMethod = ({
              get: (obj, prop) => Reflect.has(obj, prop)
              ? Reflect.get(obj, prop)
              : Reflect.has(obj[__methods], prop)
              ? Reflect.get(obj[__methods], prop)
              : console.log(`No #${prop} property exists.`)
              });

              const obj = Object.create(null);
              const createInterface = (...props) => new Proxy(
              Object.assign(obj, {
              ...props,
              [__methods]: {
              id: () => createInterface (...props),
              toString: () => `Interface(${ props.toString() })`
              }
              })
              , missingMethod
              );

              const interface = createInterface(0, 1, 2);
              interface.id(); //works
              console.log(interface.toString());





              Another possibility would be for the getter to do a hasOwnProperty check instead of a Reflect.has check (Reflect.has is basically the same as in, and 'toString' will be in almost any object):



              get: (obj, prop) => obj.hasOwnProperty(prop)





              const __methods = Symbol.for("__methods");

              const missingMethod = ({
              get: (obj, prop) => obj.hasOwnProperty(prop)
              ? Reflect.get(obj, prop)
              : Reflect.has(obj[__methods], prop)
              ? Reflect.get(obj[__methods], prop)
              : console.log(`No #${prop} property exists.`)
              });
              const createInterface = (...props) => new Proxy({
              ...props,
              [__methods]: {
              id: () => createInterface (...props),
              toString: () => `Interface(${ props.toString() })`,
              }
              }, missingMethod);

              const interface = createInterface(0, 1, 2);
              interface.id(); //works
              console.log(interface.toString());





              A third possibility would be to make sure the property found by the initial Reflect.has is not from an Object.prototype method:



              get: (obj, prop) => Reflect.has(obj, prop) && Reflect.get(obj, prop) !== Object.prototype[prop]





              const __methods = Symbol.for("__methods");

              const missingMethod = ({
              get: (obj, prop) => Reflect.has(obj, prop) && Reflect.get(obj, prop) !== Object.prototype[prop]
              ? Reflect.get(obj, prop)
              : Reflect.has(obj[__methods], prop)
              ? Reflect.get(obj[__methods], prop)
              : console.log(`No #${prop} property exists.`)
              });

              const createInterface = (...props) => new Proxy({
              ...props,
              [__methods]: {
              id: () => createInterface (...props),
              toString: () => `Interface(${ props.toString() })`
              }
              }, missingMethod);

              const interface = createInterface(0, 1, 2);
              interface.id(); //works
              console.log(interface.toString());








              share|improve this answer


























                4












                4








                4







                The problem is that accessing interface.toString first goes through



                get: (obj, prop) => Reflect.has(obj, prop)
                ? Reflect.get(obj, prop)
                : Reflect.has(obj[__methods], prop)
                ...


                You're expecting interface.toString to fall through the ternary here and get to the _methods, but Reflect.has(obj, 'toString') will evaluate to true because of Object.prototype.toString. Then, invoking that function on the object goes through the proxy's getter operation again, searching for a #toStringTag to call. The getter goes through all its ternaries and finds nothing, so it throws on the line



                console.log(`No #${prop} property exists.`)


                because prop is a symbol and cannot be concatenated.



                One possibility would be to use an object that does not inherit from Object.prototype:



                const obj = Object.create(null);
                const createInterface = (...props) => new Proxy(
                Object.assign(obj, {
                ...props,
                [__methods]: {
                id: () => createInterface (...props),
                toString: () => `Interface(${ props.toString() })`
                }
                })
                , missingMethod
                );





                const __methods = Symbol.for("__methods");

                const missingMethod = ({
                get: (obj, prop) => Reflect.has(obj, prop)
                ? Reflect.get(obj, prop)
                : Reflect.has(obj[__methods], prop)
                ? Reflect.get(obj[__methods], prop)
                : console.log(`No #${prop} property exists.`)
                });

                const obj = Object.create(null);
                const createInterface = (...props) => new Proxy(
                Object.assign(obj, {
                ...props,
                [__methods]: {
                id: () => createInterface (...props),
                toString: () => `Interface(${ props.toString() })`
                }
                })
                , missingMethod
                );

                const interface = createInterface(0, 1, 2);
                interface.id(); //works
                console.log(interface.toString());





                Another possibility would be for the getter to do a hasOwnProperty check instead of a Reflect.has check (Reflect.has is basically the same as in, and 'toString' will be in almost any object):



                get: (obj, prop) => obj.hasOwnProperty(prop)





                const __methods = Symbol.for("__methods");

                const missingMethod = ({
                get: (obj, prop) => obj.hasOwnProperty(prop)
                ? Reflect.get(obj, prop)
                : Reflect.has(obj[__methods], prop)
                ? Reflect.get(obj[__methods], prop)
                : console.log(`No #${prop} property exists.`)
                });
                const createInterface = (...props) => new Proxy({
                ...props,
                [__methods]: {
                id: () => createInterface (...props),
                toString: () => `Interface(${ props.toString() })`,
                }
                }, missingMethod);

                const interface = createInterface(0, 1, 2);
                interface.id(); //works
                console.log(interface.toString());





                A third possibility would be to make sure the property found by the initial Reflect.has is not from an Object.prototype method:



                get: (obj, prop) => Reflect.has(obj, prop) && Reflect.get(obj, prop) !== Object.prototype[prop]





                const __methods = Symbol.for("__methods");

                const missingMethod = ({
                get: (obj, prop) => Reflect.has(obj, prop) && Reflect.get(obj, prop) !== Object.prototype[prop]
                ? Reflect.get(obj, prop)
                : Reflect.has(obj[__methods], prop)
                ? Reflect.get(obj[__methods], prop)
                : console.log(`No #${prop} property exists.`)
                });

                const createInterface = (...props) => new Proxy({
                ...props,
                [__methods]: {
                id: () => createInterface (...props),
                toString: () => `Interface(${ props.toString() })`
                }
                }, missingMethod);

                const interface = createInterface(0, 1, 2);
                interface.id(); //works
                console.log(interface.toString());








                share|improve this answer













                The problem is that accessing interface.toString first goes through



                get: (obj, prop) => Reflect.has(obj, prop)
                ? Reflect.get(obj, prop)
                : Reflect.has(obj[__methods], prop)
                ...


                You're expecting interface.toString to fall through the ternary here and get to the _methods, but Reflect.has(obj, 'toString') will evaluate to true because of Object.prototype.toString. Then, invoking that function on the object goes through the proxy's getter operation again, searching for a #toStringTag to call. The getter goes through all its ternaries and finds nothing, so it throws on the line



                console.log(`No #${prop} property exists.`)


                because prop is a symbol and cannot be concatenated.



                One possibility would be to use an object that does not inherit from Object.prototype:



                const obj = Object.create(null);
                const createInterface = (...props) => new Proxy(
                Object.assign(obj, {
                ...props,
                [__methods]: {
                id: () => createInterface (...props),
                toString: () => `Interface(${ props.toString() })`
                }
                })
                , missingMethod
                );





                const __methods = Symbol.for("__methods");

                const missingMethod = ({
                get: (obj, prop) => Reflect.has(obj, prop)
                ? Reflect.get(obj, prop)
                : Reflect.has(obj[__methods], prop)
                ? Reflect.get(obj[__methods], prop)
                : console.log(`No #${prop} property exists.`)
                });

                const obj = Object.create(null);
                const createInterface = (...props) => new Proxy(
                Object.assign(obj, {
                ...props,
                [__methods]: {
                id: () => createInterface (...props),
                toString: () => `Interface(${ props.toString() })`
                }
                })
                , missingMethod
                );

                const interface = createInterface(0, 1, 2);
                interface.id(); //works
                console.log(interface.toString());





                Another possibility would be for the getter to do a hasOwnProperty check instead of a Reflect.has check (Reflect.has is basically the same as in, and 'toString' will be in almost any object):



                get: (obj, prop) => obj.hasOwnProperty(prop)





                const __methods = Symbol.for("__methods");

                const missingMethod = ({
                get: (obj, prop) => obj.hasOwnProperty(prop)
                ? Reflect.get(obj, prop)
                : Reflect.has(obj[__methods], prop)
                ? Reflect.get(obj[__methods], prop)
                : console.log(`No #${prop} property exists.`)
                });
                const createInterface = (...props) => new Proxy({
                ...props,
                [__methods]: {
                id: () => createInterface (...props),
                toString: () => `Interface(${ props.toString() })`,
                }
                }, missingMethod);

                const interface = createInterface(0, 1, 2);
                interface.id(); //works
                console.log(interface.toString());





                A third possibility would be to make sure the property found by the initial Reflect.has is not from an Object.prototype method:



                get: (obj, prop) => Reflect.has(obj, prop) && Reflect.get(obj, prop) !== Object.prototype[prop]





                const __methods = Symbol.for("__methods");

                const missingMethod = ({
                get: (obj, prop) => Reflect.has(obj, prop) && Reflect.get(obj, prop) !== Object.prototype[prop]
                ? Reflect.get(obj, prop)
                : Reflect.has(obj[__methods], prop)
                ? Reflect.get(obj[__methods], prop)
                : console.log(`No #${prop} property exists.`)
                });

                const createInterface = (...props) => new Proxy({
                ...props,
                [__methods]: {
                id: () => createInterface (...props),
                toString: () => `Interface(${ props.toString() })`
                }
                }, missingMethod);

                const interface = createInterface(0, 1, 2);
                interface.id(); //works
                console.log(interface.toString());








                const __methods = Symbol.for("__methods");

                const missingMethod = ({
                get: (obj, prop) => Reflect.has(obj, prop)
                ? Reflect.get(obj, prop)
                : Reflect.has(obj[__methods], prop)
                ? Reflect.get(obj[__methods], prop)
                : console.log(`No #${prop} property exists.`)
                });

                const obj = Object.create(null);
                const createInterface = (...props) => new Proxy(
                Object.assign(obj, {
                ...props,
                [__methods]: {
                id: () => createInterface (...props),
                toString: () => `Interface(${ props.toString() })`
                }
                })
                , missingMethod
                );

                const interface = createInterface(0, 1, 2);
                interface.id(); //works
                console.log(interface.toString());





                const __methods = Symbol.for("__methods");

                const missingMethod = ({
                get: (obj, prop) => Reflect.has(obj, prop)
                ? Reflect.get(obj, prop)
                : Reflect.has(obj[__methods], prop)
                ? Reflect.get(obj[__methods], prop)
                : console.log(`No #${prop} property exists.`)
                });

                const obj = Object.create(null);
                const createInterface = (...props) => new Proxy(
                Object.assign(obj, {
                ...props,
                [__methods]: {
                id: () => createInterface (...props),
                toString: () => `Interface(${ props.toString() })`
                }
                })
                , missingMethod
                );

                const interface = createInterface(0, 1, 2);
                interface.id(); //works
                console.log(interface.toString());





                const __methods = Symbol.for("__methods");

                const missingMethod = ({
                get: (obj, prop) => obj.hasOwnProperty(prop)
                ? Reflect.get(obj, prop)
                : Reflect.has(obj[__methods], prop)
                ? Reflect.get(obj[__methods], prop)
                : console.log(`No #${prop} property exists.`)
                });
                const createInterface = (...props) => new Proxy({
                ...props,
                [__methods]: {
                id: () => createInterface (...props),
                toString: () => `Interface(${ props.toString() })`,
                }
                }, missingMethod);

                const interface = createInterface(0, 1, 2);
                interface.id(); //works
                console.log(interface.toString());





                const __methods = Symbol.for("__methods");

                const missingMethod = ({
                get: (obj, prop) => obj.hasOwnProperty(prop)
                ? Reflect.get(obj, prop)
                : Reflect.has(obj[__methods], prop)
                ? Reflect.get(obj[__methods], prop)
                : console.log(`No #${prop} property exists.`)
                });
                const createInterface = (...props) => new Proxy({
                ...props,
                [__methods]: {
                id: () => createInterface (...props),
                toString: () => `Interface(${ props.toString() })`,
                }
                }, missingMethod);

                const interface = createInterface(0, 1, 2);
                interface.id(); //works
                console.log(interface.toString());





                const __methods = Symbol.for("__methods");

                const missingMethod = ({
                get: (obj, prop) => Reflect.has(obj, prop) && Reflect.get(obj, prop) !== Object.prototype[prop]
                ? Reflect.get(obj, prop)
                : Reflect.has(obj[__methods], prop)
                ? Reflect.get(obj[__methods], prop)
                : console.log(`No #${prop} property exists.`)
                });

                const createInterface = (...props) => new Proxy({
                ...props,
                [__methods]: {
                id: () => createInterface (...props),
                toString: () => `Interface(${ props.toString() })`
                }
                }, missingMethod);

                const interface = createInterface(0, 1, 2);
                interface.id(); //works
                console.log(interface.toString());





                const __methods = Symbol.for("__methods");

                const missingMethod = ({
                get: (obj, prop) => Reflect.has(obj, prop) && Reflect.get(obj, prop) !== Object.prototype[prop]
                ? Reflect.get(obj, prop)
                : Reflect.has(obj[__methods], prop)
                ? Reflect.get(obj[__methods], prop)
                : console.log(`No #${prop} property exists.`)
                });

                const createInterface = (...props) => new Proxy({
                ...props,
                [__methods]: {
                id: () => createInterface (...props),
                toString: () => `Interface(${ props.toString() })`
                }
                }, missingMethod);

                const interface = createInterface(0, 1, 2);
                interface.id(); //works
                console.log(interface.toString());






                share|improve this answer












                share|improve this answer



                share|improve this answer










                answered Dec 30 '18 at 5:33









                CertainPerformanceCertainPerformance

                82.7k144067




                82.7k144067






























                    draft saved

                    draft discarded




















































                    Thanks for contributing an answer to Stack Overflow!


                    • Please be sure to answer the question. Provide details and share your research!

                    But avoid



                    • Asking for help, clarification, or responding to other answers.

                    • Making statements based on opinion; back them up with references or personal experience.


                    To learn more, see our tips on writing great answers.




                    draft saved


                    draft discarded














                    StackExchange.ready(
                    function () {
                    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53975350%2fproxy-confusing-thistostring-with-thissymbol-tostringtag%23new-answer', 'question_page');
                    }
                    );

                    Post as a guest















                    Required, but never shown





















































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown

































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown







                    Popular posts from this blog

                    Monofisismo

                    Angular Downloading a file using contenturl with Basic Authentication

                    Olmecas