www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.bugs - [Issue 15600] New: Missing functions in a template struct cause

https://issues.dlang.org/show_bug.cgi?id=15600

          Issue ID: 15600
           Summary: Missing functions in a template struct cause linker
                    errors instead of being caught by compiler.
           Product: D
           Version: D2
          Hardware: x86_64
                OS: Linux
            Status: NEW
          Severity: major
          Priority: P1
         Component: dmd
          Assignee: nobody puremagic.com
          Reporter: epi atari8.info

Probably related to 14425 (but that one is marked as fixed).

The following is the most reduced case I could get:

pipeline.d:
----
module pipeline;

struct Pipeline(Stages...) {
  private template Component(S) {
    static if (is(S == struct))
      alias Component = S;
  }

  private template PipelineBuilder(int begin, int cur, int end) {
    static assert(begin <= end && begin <= cur && cur <= end && end <=
Stages.length,
      "Invalid parameters: " ~
      Stages.stringof ~ "[" ~ begin.stringof ~ "," ~ cur.stringof ~ "," ~
end.stringof ~ "]");
    static if (cur < end) {
      alias Cur = Stages[cur];
      alias Lhs = PipelineBuilder!(begin, begin, cur);
      alias Rhs = PipelineBuilder!(cur + 1, cur + 1, end);
      static if (is(Component!(Lhs.Impl) _Li))
        alias LhsImpl = _Li;
      static if (is(Component!(Rhs.Impl) _Ri))
        alias RhsImpl = _Ri;
      static if (begin + 1 == end && is(Cur _Impl)) {
        alias Impl = _Impl;
        pragma(msg, "Match: ", Stages[begin].stringof);
      } else static if (cur + 1 == end && is(Cur!LhsImpl _Impl)) {
        alias Impl = _Impl;
        pragma(msg, "Match: ", Stages[cur .. end].stringof);
      } else static if (cur == begin && is(Cur!RhsImpl _Impl)) {
        alias Impl = _Impl;
        pragma(msg, "Match: ", Stages[begin .. cur + 1].stringof);
      } else static if (is(Cur!(LhsImpl, RhsImpl) _Impl)) {
        alias Impl = _Impl;
        pragma(msg, "Match: ", Stages[begin .. end].stringof);
      }
      static if (cur + 1 < end) {
        alias Next = PipelineBuilder!(begin, cur + 1, end);
        static if (is(Next.Impl))
          alias PipelineBuilder = Next;
      }
    }
  }

  auto pipe(alias NextStage)() {
    return Pipeline!(Stages, NextStage)();
  }

  template ImplType() {
    alias Builder = PipelineBuilder!(0, 0, Stages.length);
    alias ImplType = Builder.Impl;
  }

  void run()() {
    alias Impl = ImplType!();
    static assert(is(Impl), "Could not build pipeline out of the following list
of stages: " ~ Stages.stringof);
    Impl impl;
    impl.run();
  }
}

auto pipe(alias Stage1)() {
  return Pipeline!Stage1();
}

struct PullPush(Source, Sink) {
  Source source = void;
  Sink sink = void;

  void run() {
    ubyte[4096] buf;
    for (;;) {
      size_t n = source.pull(buf[]);
      if (n == 0)
        break;
      if (sink.push(buf[0 .. n]) < n)
        break;
    }
  }
}

struct Foo(Source) {
  Source source;

  size_t pull(T)(T[] data) { return 0; }
  auto peek(size_t n) { return source.peek(n); }        // (1)
  void consume(size_t n) {}                             // (2)
}

struct NullSink {
  size_t push(T)(const(T[]) buf) { return buf.length; }
}

struct NullSource {
  size_t pull(T)(T[] buf) { return 0; }
}

unittest {
  auto s = pipe!NullSource.pipe!Foo.pipe!PullPush.pipe!NullSink;
  pragma(msg, s.ImplType!().stringof);                                     //
(3)
//  static assert(is(s.ImplType!() == PullPush!(Foo!NullSource, NullSink))); //
(4)
  s.run();                                                                 //
(5)
}

----

Compiling with 2.069.2 and 2.070-b2 gives the same result:

$ dmd pipeline.d -main -unittest -allinst
Match: NullSink
Match: NullSource
Match: tuple(Foo(Source))
Match: tuple((NullSource), Foo(Source), PullPush(Source, Sink), (NullSink))
Cur!(Cur!(NullSource), NullSink)
pipeline.o: In function
`_D8pipeline81__T8PullPushTS8pipeline31__T3FooTS8pipeline10NullSourceZ3FooTS8pipeline8NullSinkZ8PullPush3runMFNaNbNiNfZv':
__main.d:(.text._D8pipeline81__T8PullPushTS8pipeline31__T3FooTS8pipeline10NullSourceZ3FooTS8pipeline8NullSinkZ8PullPush3runMFNaNbNiNfZv+0x6e):
undefined reference to
`_D8pipeline31__T3FooTS8pipeline10NullSourceZ3Foo11__T4pullThZ4pullMFNaNbNiNfAhZm'
collect2: error: ld returned 1 exit status
--- errorlevel 1


The linker complains about missing function "pure nothrow  nogc  safe ulong
pipeline.Foo!(pipeline.NullSource).Foo.pull!(ubyte).pull(ubyte[])"

However, the problem is somewhere else. Uncomment (4), and you'll see that
what's actually missing is NullSource.peek();
To fix this, Foo.peek and Foo.consume ((1) and (2)) should be changed to
template functions.
Interestingly, the error is detected by compiler when the template is
instantiated directly: PullPush!(Foo!NullSource, NullSink), but not when
Pipeline.ImplType is used, although they both resolve to the same type.

--
Jan 24