As mentioned earlier, the WhoId and WhatId fields in the task are polymorphic (meaning that they can point to multiple targets. If you don't have SOQL Polymorphism , as @eyescream describes, the only fields you can request are those that are on the 'Name' object, but there are exceptions that are described in this document.
Example 1: select Who.FirstName, Who.LastName, Who.Email from Task
This will return data for FirstName and LastName if Contact / Lead has these fields, but as stated in the above document, it will never return results for Who.Email, since none of the two possible Who (Contact, Lead) targets is a user object.
Example 2: select Owner.Name, Owner.Email from Case
If you use Queues with a Case object, the owner of the case can be either a user or a group (the record of which is a type). Since both Name and Email are in the Name object, the above request always returns data for the name, and if the target object is a user, it will return data for Owner.Email.
Example 3: select What.Name, WhatId from Task where What.Type in ('Case','Account','Solution')
This is a very interesting example, because the "Name" field of the Case object is actually CaseNumber , and not the name --- there are several other standard objects with "non-standard" (ironic) name fields, for example. SolutionTitle, and then there are others that don't even have Name fields, like CampaignMember . HOWEVER, one of Salesforce’s great features with polymorphic fields technically points to the Name object that the above query will return results for Cases! If Case CaseNumber is 00001234, then What.Name in the above query will return 00001234, while for the account it will return the account name, and for the solution it will return SolutionTitle.
It is also useful to note here that you can limit which targets are returned by filtering in the Type field, which is a special field in the Name object.