Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Issue 7619 - Infer deprecated for template instances #10677

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

MoonlightSentinel
Copy link
Contributor

@MoonlightSentinel MoonlightSentinel commented Dec 18, 2019

Template instances may use deprecated symbols that were passed as
template parameters or selected based on an instance-specific condition.
Previously DMD raised deprecation messages for the deprecated symbol at
the point of instantiation AND substitution inside of the template code.
The latter is cannot be suppressed by using deprecated because that
will only apply to certain instantiations.

This commit changes the behavior to check whether a deprecated symbol
is used inside of a template instance and marks the instance as
deprecated if found. That way only the actual usage of the instance
will raise deprecation messages.

Ideally this should only apply to deprecated symbols that depend on the
template parameters but those dependencies are not traceable in general
(static if, compiles, ...).


The current behaviour blocks dlang/phobos#7252 because -checkaction=context may instantiate templates with deprecated types (e.g. cfloat) in deprecated unittests.

@MoonlightSentinel MoonlightSentinel changed the title Issue 7619 - Broken deprecated feature with template function Fix Issue 7619 - Broken deprecated feature with template function Dec 19, 2019
@MoonlightSentinel MoonlightSentinel force-pushed the depr-template branch 2 times, most recently from 329bf9c to ed3746c Compare December 25, 2019 19:04
@MoonlightSentinel
Copy link
Contributor Author

@Geod24 Could you have a look?

@MoonlightSentinel MoonlightSentinel force-pushed the depr-template branch 4 times, most recently from 6723367 to 6bcba3d Compare January 11, 2020 21:26
@MoonlightSentinel
Copy link
Contributor Author

MoonlightSentinel commented Jan 11, 2020

Is there a way to detect whether a symbol depends on a template parameter?
E.g. that a and b are different in the following example:

deprecated struct S {}

void foo(T)()
{
    T a;
    S b;
}

alias bar = foo!S;

@Geod24
Copy link
Member

Geod24 commented Jan 12, 2020

Is there a way to detect whether a symbol depends on a template parameter?

The resolution happens quite early. The TemplateInstance installs the correct types as aliases in the scope (

dmd/src/dmd/dtemplate.d

Lines 2237 to 2330 in 49dfbe5

/**************************************************
* Declare template parameter tp with value o, and install it in the scope sc.
*/
RootObject declareParameter(Scope* sc, TemplateParameter tp, RootObject o)
{
//printf("TemplateDeclaration.declareParameter('%s', o = %p)\n", tp.ident.toChars(), o);
Type ta = isType(o);
Expression ea = isExpression(o);
Dsymbol sa = isDsymbol(o);
Tuple va = isTuple(o);
Declaration d;
VarDeclaration v = null;
if (ea && ea.op == TOK.type)
ta = ea.type;
else if (ea && ea.op == TOK.scope_)
sa = (cast(ScopeExp)ea).sds;
else if (ea && (ea.op == TOK.this_ || ea.op == TOK.super_))
sa = (cast(ThisExp)ea).var;
else if (ea && ea.op == TOK.function_)
{
if ((cast(FuncExp)ea).td)
sa = (cast(FuncExp)ea).td;
else
sa = (cast(FuncExp)ea).fd;
}
if (ta)
{
//printf("type %s\n", ta.toChars());
auto ad = new AliasDeclaration(Loc.initial, tp.ident, ta);
ad.wasTemplateParameter = true;
d = ad;
}
else if (sa)
{
//printf("Alias %s %s;\n", sa.ident.toChars(), tp.ident.toChars());
auto ad = new AliasDeclaration(Loc.initial, tp.ident, sa);
ad.wasTemplateParameter = true;
d = ad;
}
else if (ea)
{
// tdtypes.data[i] always matches ea here
Initializer _init = new ExpInitializer(loc, ea);
TemplateValueParameter tvp = tp.isTemplateValueParameter();
Type t = tvp ? tvp.valType : null;
v = new VarDeclaration(loc, t, tp.ident, _init);
v.storage_class = STC.manifest | STC.templateparameter;
d = v;
}
else if (va)
{
//printf("\ttuple\n");
d = new TupleDeclaration(loc, tp.ident, &va.objects);
}
else
{
assert(0);
}
d.storage_class |= STC.templateparameter;
if (ta)
{
Type t = ta;
// consistent with Type.checkDeprecated()
while (t.ty != Tenum)
{
if (!t.nextOf())
break;
t = (cast(TypeNext)t).next;
}
if (Dsymbol s = t.toDsymbol(sc))
{
if (s.isDeprecated())
d.storage_class |= STC.deprecated_;
}
}
else if (sa)
{
if (sa.isDeprecated())
d.storage_class |= STC.deprecated_;
}
if (!sc.insert(d))
error("declaration `%s` is already defined", tp.ident.toChars());
d.dsymbolSemantic(sc);
/* So the caller's o gets updated with the result of semantic() being run on o
*/
if (v)
o = v._init.initializerToExpression();
return o;
}
).

There is a wasTemplateParameter that was added very recently (a7dbe96), but IMHO it's still a hack to work around the fact that aliases are just considered transparent when they clearly are not.

@Geod24
Copy link
Member

Geod24 commented Jan 12, 2020

I'll have to look deeper to be sure, but instinctively I'd say the result should happen at the Scope level, not by adding something to isDeprecated. Because relying on the message being output somewhere else is quite brittle, and marking the template instance as deprecated would not work either (as TemplateInstance gets reused).

@MoonlightSentinel
Copy link
Contributor Author

MoonlightSentinel commented Jan 13, 2020

Is there a way to detect whether a symbol depends on a template parameter?

The resolution happens quite early. The TemplateInstance installs the correct types as aliases in the scope ...

Thanks, I'll look into that.

I'll have to look deeper to be sure, but instinctively I'd say the result should happen at the Scope level, not by adding something to isDeprecated. Because relying on the message being output somewhere else is quite brittle, and marking the template instance as deprecated would not work either (as TemplateInstance gets reused).

Not sure if i undertsood you correctly, but infering deprecated for the template scope would also hide valid deprecation messages e.g. for the usage of S in the previous example.

I originally chose checkDeprecated because it's the smallest common denominator for all the different things that can be templated. Normally i would agree on the error messages but the change only intends to stop additional checks (the template arguments are evaluated anyway at the callsite)

@Geod24
Copy link
Member

Geod24 commented Jan 13, 2020

Not sure if i undertsood you correctly, but infering deprecated for the template scope would also hide valid deprecation messages e.g. for the usage of S in the previous example.

Which line in the test are you referring to ?

@MoonlightSentinel
Copy link
Contributor Author

MoonlightSentinel commented Jan 13, 2020

Which line in the test are you referring to ?

void foo(T)()
{
    T a;
    S b; // <---
}

@Geod24
Copy link
Member

Geod24 commented Jan 13, 2020

Well if foo is instantiated from a deprecated scope you don't want to show that message do you ?

@MoonlightSentinel
Copy link
Contributor Author

Well if foo is instantiated from a deprecated scope you don't want to show that message do you ?

Ideally yes because the templated symbol is using deprecated stuff while not being deprecated itself.
But inferring deprecated might be necessary to handle deprecated symbols obtained from template parameters, e.g.

void bar(alias f)()
{
    alias F = typeof(f());
    F var = f();
    ....
}

deprecated S func() {}

alias bad = bar!func;

F should not raise deprecation warnings but that would require to track these dependencies and which would probably require a lot of work.

@Geod24
Copy link
Member

Geod24 commented Jan 13, 2020

Ideally yes because the templated symbol is using deprecated stuff while not being deprecated itself.

However it will trigger a deprecation warning. And if the other warning is fixed, this one will show up.
Personally I think triggering a deprecation would be a bad idea. Consider the following:

template Transmogrify (T)
{
    static if (is(typeof(T.foobar)))
        alias Transmogriphy = FooBarTransmogriphier;
    else
        alias Transmogriphy = DefaultTransmogriphier;
}

Now if you deprecate the T with foobar and FooBarTransmogriphier, the deprecation will show twice. Not so annoying you say.
But when this template is called from a deprecated template, suddenly the message becomes redundant. Because during a deprecation, you still want to provide the same functionality (not break user's code), but advise the user to move to another symbol.
When working on libraries, I often ended up deprecating a bunch of symbols that were related. We had to sometimes resort to dirty tricks to avoid triggering a deprecation message in user code (e.g. extern(C) prototypes), because we still wanted to expose the same set of functionalities to user code.

Having some deprecations shows but not other would go against this. If we could do dependency tracking it'd be great, but DMD was not built with this in mind so it's a huge undertaking in itself.

@MoonlightSentinel
Copy link
Contributor Author

Okay, I think I got your point now. Assuming your example above, using Transmogrify!S should issue no warnings if inside a deprecated scope and flag the deprecated template instance otherwise (?).

@MoonlightSentinel
Copy link
Contributor Author

Guess I'll try to infer deprecated for templates as it seems to be the most feasable solution.
It might hide deprecation messages in very rare, hypothetical cases but least does not require a significant rewrite.

@@ -67,7 +64,7 @@ deprecated template Vaz(T)
/*
TEST_OUTPUT:
---
fail_compilation/diag14875.d(75): Error: static assert: `0` is false
fail_compilation/diag14875.d(72): Error: static assert: `0` is false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just remove this and use -de ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can do but I usually try to keep changes to existing tests as small as possible

@@ -36,27 +36,24 @@ template Baz(T)
/*
TEST_OUTPUT:
---
fail_compilation/diag14875.d(47): Deprecation: class `diag14875.Dep` is deprecated
fail_compilation/diag14875.d(51): Deprecation: variable `diag14875.depVar` is deprecated
fail_compilation/diag14875.d(44): Deprecation: class `diag14875.Dep` is deprecated
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually looking at the issue, I think we should just merge both test cases, since this does not really test the original issue anymore.

@MoonlightSentinel
Copy link
Contributor Author

frontend.d currently fails because this line is inferred as deprecated:

versionIdentifiers.each!(VersionCondition.addGlobalIdent);

This originates from this deprecated overload of addGlobalIdent which isn't even used in frontend.d

dmd/src/dmd/cond.d

Lines 471 to 475 in 129f88f

deprecated("Kept for C++ compat - Use the string overload instead")
static void addGlobalIdent(const(char)* ident)
{
addGlobalIdent(ident[0 .. ident.strlen]);
}

Seems like hasDeprecatedParameters needs to handle overloads somehow ...

@12345swordy
Copy link
Contributor

What is going on here?

@MoonlightSentinel
Copy link
Contributor Author

Worked on a lot of other stuff but I want to finish this PR in the future.

Template instances may use deprecated symbols that were passed as
template parameters or selected based on an instance-specific condition.
Previously DMD raised deprecation messages for the deprecated symbol at
the point of instantiation AND substitution inside of the template code.
The latter is cannot be suppressed by using `deprecated` because that
will only apply to certain instantiations.

This commit changes the behavior to check whether a deprecated symbol
is used inside of a template instance and marks the instance as
`deprecated` if found. That way only the actual usage of the instance
will raise deprecation messages.

Ideally this should only apply to deprecated symbols that depend on the
template parameters but those dependencies are not traceable in general
(`static if`, `compiles`, ...).
@dlang-bot
Copy link
Contributor

Thanks for your pull request and interest in making D better, @MoonlightSentinel! We are looking forward to reviewing it, and you should be hearing from a maintainer soon.
Please verify that your PR follows this checklist:

  • My PR is fully covered with tests (you can see the coverage diff by visiting the details link of the codecov check)
  • My PR is as minimal as possible (smaller, focused PRs are easier to review than big ones)
  • I have provided a detailed rationale explaining my changes
  • New or modified functions have Ddoc comments (with Params: and Returns:)

Please see CONTRIBUTING.md for more information.


If you have addressed all reviews or aren't sure how to proceed, don't hesitate to ping us with a simple comment.

Bugzilla references

Auto-close Bugzilla Severity Description
7619 normal Broken deprecated feature with template function

Testing this PR locally

If you don't have a local development environment setup, you can use Digger to test this PR:

dub run digger -- build "master + dmd#10677"

@MoonlightSentinel MoonlightSentinel changed the title Fix Issue 7619 - Broken deprecated feature with template function Fix Issue 7619 - Infer deprecated for template instances Nov 18, 2021
@RazvanN7
Copy link
Contributor

@MoonlightSentinel It would be great if would manage to finish this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants